Java多線程、並發編程知識點小結

來源:互聯網
上載者:User

標籤:android   java   多線程   並發   

1、線程的狀態

   1.1建立 線程 的兩種方式,介面和線程類。利用介面的好處:更好的體現物件導向的思想,可以避免由於Java的單繼承特性而帶來的局限;
  增強程式的健壯性,代碼能夠被多個線程共用,代碼與資料是獨立的;(同步問題)適合多個相同程式碼的線程區處理同一資源的情況。


   1.2線程就緒等待調度運行start()方法。


1.3線程的中斷

這裡需要注意的是,如果只是單純的調用interrupt()方法,線程並沒有實際被中斷,會繼續往下執行。


1.4、線程掛起和恢複(掛起還擁有對象鎖,死結)
線程的掛起和恢複實現的正確方法是:通過設定標誌位,讓線程在安全的位置掛起


1.5 利用多線程類比同步運行用jion方法,mThread.jion()表示該線程運行完畢後,在運行調用它的線程。


1.6 sleep 休眠

當線程執行Thread.sleep()時,它一直阻塞到指定的毫秒時間之後,或者阻塞被另一個線程打斷;


1.7stop線程停止

stop方法突然終止線程(持有這些鎖必定有某種合適的理由——也許是阻止其他線程訪問尚未處於一致性狀態的資料,
突然釋放鎖可能使某些對象中的資料處於不一致狀態)


1.8線程可以阻塞於四種狀態:


當線程執行Thread.sleep()時,它一直阻塞到指定的毫秒時間之後,或者阻塞被另一個線程打斷;


當線程碰到一條wait()語句時,它會一直阻塞到接到通知(notify())、被中斷或經過了指定毫秒時間為止(若制定了逾時值的話)
線程阻塞與不同I/O的方式有多種。常見的一種方式是InputStream的read()方法,該方法一直阻塞到從流中讀取一個位元組的資料為止,它可以無限阻塞,因此不能指定逾時時間
線程也可以阻塞等待擷取某個對象鎖的排他性存取權限(即等待獲得synchronized語句必須的鎖時阻塞)。


2、線程的種類

守護線程與線程阻塞的四種情況
  Java中有兩類線程:User Thread(使用者線程)、Daemon Thread(守護線程) 
使用者可以用Thread的setDaemon(true)方法設定當前線程為守護線程。


守護線程是否已經完成了預期的服務任務。一旦所有的使用者線程退出了,虛擬機器也就退出運行了。
 因此,不要在守護線程中執行商務邏輯操作(比如對資料的讀寫等)。、




setDaemon(true)必須在調用線程的start()方法之前設定,否則會跑出IllegalThreadStateException異常。
在守護線程中產生的新線程也是守護線程。  
不要認為所有的應用都可以分配給守護線程來進行服務,比如讀寫操作或者計算邏輯。 



3、線程所操作的資料

同步問題:


4、可重新進入內建鎖
每個Java對象都可以用做一個實現同步的鎖,這些鎖被稱為內建鎖或監視器鎖。
線程在進入同步代碼塊之前會自動擷取鎖,並且在退出同步代碼塊時會自動釋放鎖。
獲得內建鎖的唯一途徑就是進入由這個鎖保護的同步代碼塊或方法。


某一個持有同步對象鎖的線程可以多次進入這個同步代碼塊或方法。即同步對象鎖可以重入!
同一個線程在調用本類中其他synchronized方法/塊或父類中的synchronized方法/塊時,都不會阻礙該線程地執行,因為互斥鎖時可重新進入的。






6、Java記憶體模型
在當前的Java記憶體模型下,線程可以把變數儲存在本地記憶體(比如機器的寄存器)中,而不是直接在主存中進行讀寫。(本地記憶體+共用主存)


Volatile修飾的成員變數在每次被線程訪問時,都強迫從共用記憶體中重讀該成員變數的值。而且,當成員變數發生變化時,強迫線程將變化值回寫到共用記憶體。
這樣在任何時刻,兩個不同的線程總是看到某個成員變數的同一個值。
而volatile關鍵字就是提示JVM:對於這個成員變數,不能儲存它的私人拷貝,而應直接與共用成員變數互動。


volatile型變數的特殊規則:


1、保證此變數對所有線程的可見度。需要注意,volatile變數的寫操作除了對它本身的讀操作可見外,volatile寫操作之前的所有共用變數均對volatile讀操作之後的操作可見
2、禁止指令重排序最佳化
final域能確保初始化過程的安全性,從而可以不受限制地訪問不可變對象,並在共用這些對象時無須同步


因此在編碼時,不需要將long和double變數專門聲明為volatile。
主記憶體與工作記憶體


Java記憶體模型規定所有的變數都儲存在主記憶體中,而每條線程還有自己的工作記憶體,線程的工作記憶體中儲存了該線程使用到的變數的主記憶體副本拷貝,
線程對變數的所有操作(讀取、賦值等)都必須在工作記憶體中進行,而不能直接讀寫主記憶體中的變數。




根據Java虛擬機器規範的規定,volatile變數依然有共用記憶體的拷貝,但是由於它特殊的操作順序性規定——從工作記憶體中讀寫資料前,
必須先將主記憶體中的資料同步到工作記憶體中,所有看起來如同直接在主記憶體中讀寫訪問一般,因此這裡的描述對於volatile也不例外




不允許一個線程丟棄它的最近的assign操作,即變數在工作記憶體中改變了之後必須把該變化同步回主記憶體。
如果對一個變數執行lock操作,將會清空工作記憶體中此變數的值,在執行引擎使用這個變數前,需要重新執行load或assign操作初始設定變數的值。
對一個變數執行unlock操作之前,必須先把此變數同步回主記憶體(


7、輕量級同步
volatile是一種稍弱的同步機制,在訪問volatile變數時不會執行加鎖操作,也就不會執行線程阻塞,因此volatilei變數是一種比synchronized關鍵字更輕量級的同步機制。




使用建議:在兩個或者更多的線程需要訪問的成員變數上使用volatile。當要訪問的變數已在synchronized代碼塊中,或者為常量時,沒必要使用volatile。
由於使用volatile屏蔽掉了JVM中必要的代碼最佳化,所以在效率上比較低,因此一定在必要時才使用此關鍵字。




假如有兩個線程分別讀寫volatile變數時,線程A寫入了某volatile變數,線程B在讀取該volatile變數時,便能看到線程A對該volatile變數的寫入操作,
關鍵在這裡,它不僅會看到對該volatile變數的寫入操作,A線程在寫volatile變數之前所有可見的共用變數,在B線程讀同一個volatile變數後,都將立即變得對B線程可見。




happen—before規則介紹
其意思就是說,在發生操作B之前,操作A產生的影響都能被操作B觀察到,“影響”包括修改了記憶體中共用變數的值、發送了訊息、調用了方法等,它與時間上的先後發生基本沒有太大關係。
線程啟動規則:Thread對象的start()方法happen—before此線程的每一個動作。






8、同步鎖說明
當線程擁有這個鎖標記時才能訪問這個資源,沒有鎖標記便進入鎖池。任何一個對象系統都會為其建立一個互斥鎖,
這個鎖是為了分配給線程的,防止打斷原子操作。每個對象的鎖只能分配給一個線程,因此叫做互斥鎖。


如果同一個方法內同時有兩個或更多線程,則每個線程有自己的局部變數拷貝。
類的每個執行個體都有自己的對象層級鎖


訪問同一個類的不同執行個體對象中的同步代碼塊,不存在阻塞等待擷取對象鎖的問題,
因為它們擷取的是各自執行個體的對象層級鎖,相互之間沒有影響。


持有一個對象層級鎖不會阻止該線程被交換出來,也不會阻塞其他線程訪問同一樣本對象中的非synchronized代碼。


持有對象層級鎖的線程會讓其他線程阻塞在所有的synchronized代碼外。


使用synchronized(obj)同步語句塊,可以擷取指定對象上的對象層級鎖。




類層級鎖被特定類的所有樣本共用,它用於控制對static成員變數以及static方法的並發訪問。具體用法與對象層級鎖相似。






10、多線程環境中安全使用集合API




最初設計的Vector和Hashtable是多安全執行緒的。


  在Collections類 中有多個靜態方法,它們可以擷取通過同步方法封裝非同步集合而得到的集合:
   


     public static List synchronizedList(list l)


List list = Collection.synchronizedList(new ArrayList());
     注意,ArrayList執行個體馬上封裝起來,不存在對未同步化ArrayList的直接引用(即直接封裝匿名執行個體)。
這是一種最安全的途徑。如果另一個線程要直接引用ArrayList執行個體,它可以執行非同步修改。


從記憶體可見度的角度看,寫入volatile變數相當於退出同步代碼塊,而讀取volatile變數相當於進入同步代碼塊。




、volatile變數是一種稍弱的同步機制在訪問volatile變數時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變數是一種比synchronized關鍵字更輕量級的同步機制。




原因是聲明為volatile的簡單變數如果當前值與該變數以前的值相關,那麼volatile關鍵字不起作用,也就是說如下的運算式都不是原子操作:“count++”、“count = count+1”。


若且唯若滿足以下所有條件時,才應該使用volatile變數:
     1、對變數的寫入操作不依賴變數的當前值,或者你能確保只有單個線程更新變數的值。
     2、該變數沒有包含在具有其他變數的不變式中。






三、死結問題
遵循以下原則有助於規避死結: 




   1、只在必要的最短時間內持有鎖,考慮使用同步語句塊代替整個同步方法;
     2、盡量編寫不在同一時刻需要持有多個鎖的代碼,如果不可避免,則確保線程持有第二個鎖的時間盡量短暫;
     3、建立和使用一個大鎖來代替若干小鎖,並把這個鎖用於互斥,而不是用作單個對象的對象層級鎖;


四、線程通訊




在調用wait()之前,線程必須要獲得該對象的對象層級鎖,即只能在同步方法或同步塊中調用wait()方法。進入wait()方法後,當前線程釋放鎖。
notify() 該方法也要在同步方法或同步塊中調用,即在調用前,線程也必須要獲得該對象的對象層級鎖,的如果調用notify()時沒有持有適當的鎖,也會拋出IllegalMonitorStateException。


notify後,當前線程不會馬上釋放該對象鎖,wait所在的線程並不能馬上擷取該對象鎖,要等到程式退出synchronized代碼塊後,當前線程才會釋放鎖,wait所在的線程也才可以擷取該對象鎖),
但不驚動其他同樣在等待被該對象notify的線程們。當第一個獲得了該對象鎖的wait線程運行完畢以後,它會釋放掉該對象鎖,此時如果該對象沒有再次使用notify語句,則即便該對象已經空閑,
其他wait狀態等待的線程由於沒有得到該對象的通知,會繼續阻塞在wait狀態,直到這個對象發出一個notify或notifyAll。這裡需要注意:它們等待的是被notify或notifyAll,而不是鎖。


這與下面的notifyAll()方法執行後的情況不同。 




 notifyAll使所有原來在該對象上wait的線程統統退出wait的狀態(即全部被喚醒,不再等待notify或notifyAll,但由於此時還沒有擷取到該對象鎖,因此還不能繼續往下執行),
變成等待擷取該對象上的鎖,一旦該對象鎖被釋放(notifyAll線程退出調用了notifyAll的synchronized代碼塊的時候),他們就會去競爭。
如果其中一個線程獲得了該對象鎖,它就會繼續往下執行,在它退出synchronized代碼塊,釋放鎖後,其他的已經被喚醒的線程將會繼續競爭擷取該鎖,一直進行下去,直到所有被喚醒的線程都執行完畢。






 如果線程調用了對象的wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。
   當有線程調用了對象的notifyAll()方法(喚醒所有wait線程)或notify()方法(只隨機喚醒一個wait線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。
   優先順序高的線程競爭到對象鎖的機率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調用wait()方法,它才會重新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,
直到執行完了synchronized代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。








 總結:在使用線程的等待/通知機制時,一般都要配合一個boolean變數值(或者其他能夠判斷真假的條件),在notify之前改變該boolean變數的值,讓wait返回後能夠退出while迴圈
(一般都要在wait方法外圍加一層while迴圈,以防止早期通知),或在通知被遺漏後,不會被阻塞在wait方法處。這樣便保證了程式的正確性。






五、並發新特性
1、
 一般來說,CachedTheadPool在程式執行過程中通常會建立與所需數量相同的線程,然後在它回收舊線程時停止建立新線程,
因此它是合理的Executor的首選,只有當這種方式會引發問題時(比如需要大量長時間連線導向的線程時),才需要考慮用FixedThreadPool。




服務端連線導向:public static ExecutorService newFixedThreadPool(int nThreads)




Executor執行Runnable任務
Executor執行Callable任務




在Java 5之後,任務分兩類:一類是實現了Runnable介面的類,一類是實現了Callable介面的類。兩者都可以被ExecutorService執行,
但是Runnable任務沒有返回值,而Callable任務有返回值。並且Callable的call()方法只能通過ExecutorService的submit(Callable<T> task) 方法來執行,
並且返回一個 <T>Future<T>,是表示任務等待完成的 Future。




  Callable介面類似於Runnable,兩者都是為那些其執行個體可能被另一個線程執行的類設計的。但是 Runnable 不會返回結果,並且無法拋出經過檢查的異常而Callable又返回結果,
而且當擷取返回結果時可能會拋出異常。Callable中的call()方法類似Runnable的run()方法,區別同樣是有返回值,後者沒有。




 當將一個Callable的對象傳遞給ExecutorService的submit方法,則該call方法自動在一個線程上執行,並且會返回執行結果Future對象。同樣,將Runnable的對象傳遞給ExecutorService的submit方法,
則該run方法自動在一個線程上執行,並且會返回執行結果Future對象,但是在該Future對象上調用get方法,將返回null。


2、自訂線程池


ThreadPoolExecutor類建立,它有多個構造方法來建立線程池


  BlockingQueue<Runnable>




並發新特性—Lock鎖和條件變數





Java多線程、並發編程知識點小結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.