《JAVA并發(fā)編程的藝術(shù)》筆記(review中)

進程與線程

1.進程:
? ? ? ?進程是資源分配的基本單位,又是調(diào)度運行的基本單位,是系統(tǒng)中并發(fā)執(zhí)行的單位。進程是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動。

① 進程是一個實體。每一個進程都有它自己的地址空間,一般情況下,包括代碼段、數(shù)據(jù)區(qū)和堆棧;
② 進程是一個“執(zhí)行中的程序”。程序是一個沒有生命的實體,只有處理器賦予程序生命時,它才能成為一個活動的實體,我們稱其為進程。

2.線程:
? ? ? ?線程是進程中執(zhí)行運算的最小單位,通常在一個進程中可以包含若干個線程,這若干個線程可以被同時調(diào)度到多個CPU上運行,線程可以利用進程所擁有的資源。
? ? ? ?多線程是為了同步完成多項任務,不是為了提高運行效率,而是為了提高資源使用效率來提高系統(tǒng)的效率。線程是在同一時間需要完成多項任務的時候?qū)崿F(xiàn)的。

線程安全問題

? ? ? ?在沒有充足同步的情況下,由于線程內(nèi)存模型及多線程中的操作執(zhí)行順序的不可預測性的緣故,對對象中的可變狀態(tài)或類的共享狀態(tài)執(zhí)行操作時,可能會產(chǎn)生奇怪的結(jié)果。

1.內(nèi)存屏障

內(nèi)存屏障

2.volatile 修飾符

? ? ? ?保證了共享變量的可見性,當一個線程修改一個共享變量時,另外一個線程能夠讀到被修改的值,不保證其原子性。
? ? ? ?在對volatile 變量進行寫操作時會多出lock匯編代碼,該指令會引發(fā)兩件事情:
    1.lock前綴指令會引起處理器將緩存行寫回到內(nèi)存,以前的處理器通過LOCK#信號鎖總線,而目前的處理器則是通過緩存鎖定,鎖定這塊內(nèi)存區(qū)域的緩存;
    2.上述寫回操作會使其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效;通過緩存一致性協(xié)議,每個處理器通過嗅探在總線上傳播的數(shù)據(jù)(對共享數(shù)據(jù)的修改)來查看自己緩存的值是否過期。

可見性:對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入;
原子性:對任意volatile變量的單個讀/寫操作具有原子性,但類似volatile++這種復合操作不具有原子性;

volatile寫-讀的內(nèi)存語義
? ? ? ?當寫一個volatile變量時,JMM會把該線程的本地內(nèi)存中的共享變量刷新到主內(nèi)存;
? ? ? ?當讀一個volatile變量時,JMM會直接從主內(nèi)存中讀取共享變量;
volatile寫-讀的內(nèi)存語義實現(xiàn)
   StoreStore屏障-volatile寫-StoreLoad屏障;
   volatile讀-LoadLoad屏障-LoadStore屏障;

volatile重排序規(guī)則表

? ? ? ?只要volatile變量和普通變量之間的重排序可能會破壞volatile的內(nèi)存語義,這種重排序就會被編譯器重排序規(guī)則和處理器內(nèi)存屏蔽插入策略禁止;

3.synchronized

? ? ? ?靜態(tài)同步方法鎖住Class對象,普通同步方法鎖住當前對象,同步代碼塊鎖住配置的對象。JVM基于進入和退出Monitor對象來實現(xiàn)同步。同步代碼塊→monitorenter,monitorexit;同步方法→ACC_SYNCHRONIZED。任意線程對Object的同步訪問,首先要獲得Object的監(jiān)視器,如果獲取失敗,線程進入同步隊列,線程狀態(tài)變?yōu)锽locked。當訪問Object的前驅(qū)釋放了鎖,則該釋放操作會喚醒阻塞隊列中的線程,使其重新嘗試對監(jiān)視器的獲取。
? ? ? ?釋放鎖和獲取鎖與volatile寫-讀具有相同的內(nèi)存語義,在多處理器上進行CAS時,會在cmpxchg加上lock前綴。
? ? ? ?lock前綴:確保對內(nèi)存的讀-改-寫操作原子執(zhí)行;禁止該指令與之前和之后的讀寫指令重排序;把寫緩存區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中。

4.JAVA對象頭

? ? ? ?如果對象是數(shù)組,則對象頭尾3個字寬,否則為2個字寬;JAVA對象頭里的MarkWord里默認存儲對象的HashCode、分代年齡和鎖標記位。

MarkWord

5.鎖的升級和對比

鎖一共有4種狀態(tài),由低到高分別為:無鎖、偏向鎖、輕量級鎖、重量級鎖,鎖狀態(tài)會隨著競爭情況逐漸升級,鎖可以升級但不能降級;

偏向鎖

? ? ? ?當一個線程訪問同步代碼塊并獲取鎖時,會在對象頭和棧幀的鎖記錄里面存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和釋放鎖(如果鎖仍然偏向該線程,若不則CAS操作將對象頭的偏向鎖指向該線程),只需簡單測試對象頭的偏向鎖記錄是否仍然指向該線程。
? ? ? ?偏向鎖的撤銷,出現(xiàn)競爭才釋放鎖,撤銷需要等待全局安全點,暫停并檢查用于偏向鎖的線程,若不處于活動狀態(tài)則置為無鎖狀態(tài),若線程仍活著則暫停并恢復對象頭為無鎖或標記對象不適合作為偏向鎖,然后喚醒線程;

輕量級鎖

  • 加鎖
    在當前線程的棧幀中創(chuàng)建存儲鎖記錄的空間,并復制對象頭中的Mark Word到鎖記錄中(Displaced Mark Word),然后使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針,成功則獲得鎖,失敗則表示有其他線程競爭鎖,嘗試自旋來獲取鎖;

  • 解鎖,通過CAS操作將Displaced Mark Word替換回對象頭,成功(a &b)則表示沒有競爭;失敗則鎖膨脹成了重量級鎖,喚醒等待的線程,但都會釋放鎖;
    a. 對象頭中的Mark Word中的鎖記錄是否仍然指向當前線程鎖記錄;
    b. 拷貝在當前線程鎖記錄的Mark Word信息是否與對象頭中的Mark Word一致;

    鎖原理圖

6.原子操作的實現(xiàn)

1) 處理器如何實現(xiàn)原子操作?

  • 總線鎖(開銷大),使用處理器提供的一個LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞,其與內(nèi)存的通信被鎖住,那么該處理器可以獨享共享內(nèi)存
  • 緩存鎖,保證同一時刻對某個內(nèi)存地址的操作是原子的,內(nèi)存區(qū)域如果被緩存在處理器的緩存行中,并且在LOCK操作期間被鎖定,那么當將緩存寫回內(nèi)存時,通過修改內(nèi)部的內(nèi)存地址并允許它的緩存一致性來保證操作的原子性。但是當操作的數(shù)據(jù)不能被緩存或操作的數(shù)據(jù)跨越多個緩存行時處理器會調(diào)用總線鎖定,還有部分處理器不支持緩存鎖定;

2) JAVA如何實現(xiàn)原子操作?

  • 循環(huán)CAS操作,利用處理器提供的CMPXCHG指令實現(xiàn);

CAS實現(xiàn)原子操作的三大問題:
   1.ABA問題,一個值從A→B→A變化,使用CAS檢查會發(fā)現(xiàn)值無變化,解決辦法是變量前面追加版本號;
   2.循環(huán)時間長開銷大,自旋CAS如果長時間不成功會給CPU帶來非常大的執(zhí)行開銷;
   3.只能保證一個共享變量的原子操作,將多變量合并,JDK1.5提供的AtomicReference 類來保證引用對象的原子性,可以將多個變量封裝到一個對象中來進行CAS;

  • 使用鎖機制來實現(xiàn)原子操作,保證了只有獲得鎖的線程才能夠操作鎖定的內(nèi)存區(qū)域,除了偏向鎖,JVM實現(xiàn)鎖的方式都采用了循環(huán)CAS,在進入時通過循環(huán)CAS獲取鎖,在退出同步塊時使用循環(huán)CAS來釋放鎖;

7.從源代碼到指令序列的重排序

? ? ? ?1)編譯器優(yōu)化重排序,在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序;
? ? ? ?2)指令級并行重排序,現(xiàn)代處理器采用指令級并行技術(shù)來將多條指令重疊執(zhí)行,如果不存在數(shù)據(jù)依賴,處理器可以改變語句對應機器指令的執(zhí)行順序;
? ? ? ?3)內(nèi)存系統(tǒng)重排序,由于處理器使用緩存和讀/寫緩沖區(qū),使得加載和存儲操作看上去是亂序執(zhí)行的;
? ? ? ?上述1)屬于編譯器重排序,2)3)屬于處理器重排序,可能會導致內(nèi)存可見性問題。對于1)JMM的編譯器重排序規(guī)則會禁止特定類型的重排序,
? ? ? ?對于2)和3),JMM的處理器重排序規(guī)則會要求JAVA編譯器在生成指令序列時,插入特定類型的內(nèi)存屏障指令,通過內(nèi)存屏障指令來禁止特定類型的重排序;
? ? ? ?數(shù)據(jù)依賴性:如果兩個操作訪問同一個變量,且這兩個操作有一個為寫操作,此時這2個操作之間存在數(shù)據(jù)依賴性,在重排序時,不會改變存在數(shù)據(jù)依賴關(guān)系的2個操作的執(zhí)行順序;

8.順序一致性模型&JAVA內(nèi)存模型

順序一致特性:一個線程中的所有操作必須按照程序的順序來執(zhí)行;(不管程序是否同步)所有線程都只能看到一個單一的操作執(zhí)行順序。
未同步程序的執(zhí)行特性:JMM只提供最小安全性,線程執(zhí)行時讀取的值要么是之前某個線程寫入的值,要么是默認值(0,NULL,FALSE)。為了實現(xiàn)最小安全性,JVM在堆上分配對象時,首先會對內(nèi)存空間清零,然后才會在上面分配對象,在已清理的內(nèi)存空間分配對象時,域的默認初始化已經(jīng)完成。
未同步程序在兩個模型中的差異
1)順序一致性模型保證單線程內(nèi)的操作會按程序順序執(zhí)行,而JMM不保證(臨界區(qū)代碼重排序);
2)順序一致性模型保證所有線程只能看到一致的操作執(zhí)行順序,而JMM不保證(臨界區(qū)代碼重排序);
3)JMM不保證對64位的Long和Double變量的寫操作是原子的,而順序一致性模型保證對所有的內(nèi)存讀寫操作都具有原子性;

9.final域

final域的重排序規(guī)則
 a)在構(gòu)造函數(shù)內(nèi)對一個final域的寫入,與隨后把這個被構(gòu)造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序(保證在對象引用為任意線程可見之前,對象的final域已經(jīng)被正確的初始化,保證在構(gòu)造函數(shù)內(nèi)部,不能讓這個被構(gòu)造對象的引用為其他線程所見),在final寫之后,構(gòu)造函數(shù)返回之前插入StoreStore內(nèi)存屏障;
 b)初次讀一個包含final域的對象的引用,與隨后初次讀該對象的final域,這兩個操作之間不能重排序(保證在讀一個對象的引用之前,一定會先讀包含這個final域的對象的引用),在final讀之前插入LoadLoad內(nèi)存屏障;

? ? ? ?X86處理器不會對寫-寫和間接依賴關(guān)系的操作進行重排序,所以在X86處理器中final域的讀/寫不會插入任何內(nèi)存屏障。
? ? ? ?JSR-133中,只要對象正確構(gòu)造(對象引用不會由構(gòu)造器溢出),那么不需要同步就可以保證任意線程都能看到final域在構(gòu)造函數(shù)中被初始化之后的值。

10.雙重檢查鎖定與延遲初始化

延遲初始化:推遲高開銷的對象初始化操作,在使用對象時才進行初始化。
雙重檢查鎖定:直接對getInstance()加鎖存在具體的開銷。

if(instance == null){//1  
  synchronized(Main.class){//2  
    if(instance == null){//3  
      instance = new Main()//4    
    }  
  }  
}  

存在的問題
? ? ? ?第4行,代碼讀到instance不為null時,對象可能還沒有完成初始化;創(chuàng)建一個對象可以分為三步:a.分配對象內(nèi)存空間,b.初始化對象,c.將對象引用指向剛分配的內(nèi)存地址,由于在單線程中b,c重排序不會改變執(zhí)行結(jié)果,但在多線程并發(fā)情況下,b,c的重排序可能導致線程在創(chuàng)建對象時先執(zhí)行c而導致其他線程判斷instance不為空,但此時對象初始化并未完成。

解決方案:
1) 將引用對象instance聲明為volatile,實際為禁止b,c的重排序;
2) 基于類初始化

在發(fā)生下列任意一種情況時,一個類/接口類型T將被立即初始化:

  • T是一個類,而且一個T類型的實例被創(chuàng)建(new)

  • T是一個類,且T中聲明的靜態(tài)方法被調(diào)用

  • T中聲明的一個靜態(tài)字段被賦值

  • T中聲明的一個靜態(tài)字段被調(diào)用,且該字段不是一個常量字段

  • T是一個頂級類,且一個斷言語句嵌套在T內(nèi)部被執(zhí)行

JVM在類初始化期間會獲取與該類對應的初始化鎖,并且每個線程至少獲取一次鎖來確保該類已被初始化。
類初始化過程中的同步處理:
  第一階段:通過在Class對象上同步(獲取Class對象初始化鎖),來控制類或接口的初始化。獲取鎖線程會一直阻塞直到獲取到該初始化鎖。

第二階段:線程A執(zhí)行類的初始化,同時線程B在初始化鎖對應的condition上等待;

第三階段:線程A設置state=initialized,喚醒在condition中等待的所有線程;

第四階段:線程B結(jié)束類的初始化處理;

第五階段:線程C執(zhí)行類的初始化的處理,過程同上表(不會在condition中等待,只經(jīng)歷一次鎖的獲取和釋放);

11.線程

線程是操作系統(tǒng)調(diào)度的最小單位;
1.為什么要使用多線程?

  • 更多的處理器核心,由于一個線程在同一時刻只能運行在一個處理器核心上,隨著處理器上的核心數(shù)量越來越多,使用多線程能夠同時使用多個處理器核心,顯著減少程序處理時間。
  • 更快的響應時間,使用多線程技術(shù)將數(shù)據(jù)一致性不強的操作派發(fā)給其他線程處理,最大化利用CPU時間片。
  • 更好的編程模型

2.線程的狀態(tài)

3.Daemon線程(守護線程)
? ? ? ?當一個JAVA虛擬機中不存在非Daemon線程的時候,虛擬機將會退出;通過Thread.setDaemon(true)進行設置;Daemon屬性需要在啟動線程之前設置,不能在啟動線程之后設置。當所有非Daemon線程結(jié)束,虛擬機中的所有Daemon線程需要立即終止,并不執(zhí)行finally塊。

4.構(gòu)造線程(Thread.init(…)方法)
? ? ? ?線程對象在構(gòu)造時需要提供線程所需要的屬性,如線程所屬的線程組、線程優(yōu)先級、是否時Daemon線程等;一個新構(gòu)造的線程對象是由其parent線程(創(chuàng)建新線程的線程)來進行空間分配的,child線程繼承了parent是否為Daemon、優(yōu)先級和加載資源的contextClassLoader以及可繼承的ThreadLocal,同時還會分配一個唯一ID來標識此child線程。

5.線程中斷

  • isInterrupted()判斷是否被中斷,不會影響中斷標志;如果線程處于終止狀態(tài),即使線程已經(jīng)被中斷過,依舊返回false;
  • interrupted()判斷是否被中斷,并復位中斷標志;
  • interrupt()對線程進行中斷;

? ? ? ?在拋出InterruptedException之前,虛擬機會先將該線程的中斷標志復位;
? ? ? ?安全地終止線程:通過在run()的循環(huán)中加入對線程中斷標志的判斷或者單獨設置一個標志,通過對該標志的置位來退出循環(huán),進而退出此線程;

6.過期的suspend()、resume()和stop()。suspend()暫停此線程,不釋放占有的資源(比如鎖);resume()恢復此線程;stop()停止此線程,不保證線程資源被正常釋放;

7.等待/通知機制(wait/notify)

  • notify()通知一個在對象上等待的線程,使其從wait()方法返回,返回前提是該線程獲得了對象的鎖
  • notifyAll()通知所有等待在該對象上的線程
  • wait()調(diào)用該方法的線程進入WAITING狀態(tài),釋放對象鎖,只有等待其他線程的通知或被中斷才返回
  • wait(long)超時等待一段時間,單位是毫秒,如果時間到?jīng)]有通知則返回
  • wait(long,int)對于超時時間的細粒度控制,可以達到納秒

注意:
  a 使用wait()、notify()、notifyAll()時需要先對調(diào)用對象加鎖;
  b 調(diào)用wait()方法后,線程狀態(tài)由RUNNING變?yōu)閃ATING,并將線程放入對象的等待隊列;
  c A線程調(diào)用notify()和notifyAll()后,等待線程B不會立即從wait()返回,需要A線程釋放鎖,等待線程B才有機會從wait()返回;
  d notify()方法將等待隊列的一個等待線程從等待隊列中移到同步隊列,而notifyAll()則時將等待隊列中的所有線程全部移到同步隊列,被移動線程由WATING狀態(tài)變?yōu)锽LOCKED狀態(tài);
  e 從wait()方法返回的前提時獲得了調(diào)用對象的鎖;

12.LOCK與synchronized

LOCK接口提供的synchronized關(guān)鍵字不具備的主要特性
 1.嘗試非阻塞地獲取鎖,當前線程獲取鎖時,如果此刻鎖沒有被其他線程獲取,則成功獲取該鎖;
 2.能中斷地獲取鎖,與synchronized不同,獲取鎖的線程能夠響應中斷,當獲取到鎖的線程被中斷時,會拋出中斷異常,同時釋放鎖;
 3.超時獲取鎖,在指定的截至時間之前獲取鎖,如果截至時間到仍無法獲取鎖,則返回;

13.隊列同步器(AbstractQueuedSynchronizer)

用于構(gòu)建鎖或其他同步組件的基礎(chǔ)框架,使用一個int變量表示成員狀態(tài),通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作。

1.同步隊列
? ? ? ?當前線程獲取同步狀態(tài)失敗時,同步器會將當前線程及其等待狀態(tài)等信息構(gòu)成一個NODE并將其加入同步隊列(雙向鏈表),同時會阻塞當前線程,當同步狀態(tài)釋放時,會把首節(jié)點的線程喚醒,使其再次嘗試獲取同步狀態(tài)。

2.獨占式同步狀態(tài)獲取與釋放
? ? ? ?調(diào)用同步器的acquire(int arg)方法獲取同步狀態(tài):通過調(diào)用自定義的tryacquire()來線程安全地獲取同步狀態(tài),如果失敗,則構(gòu)造同步節(jié)點并通過addWaiter(Node node)方法將該節(jié)點加入到同步隊列的尾部,最后調(diào)用acquireQueue(),使得該節(jié)點以“死循環(huán)“的方式獲取同步狀態(tài),如果獲取不到則阻塞節(jié)點中的線程,被阻塞線程的喚醒主要依靠前驅(qū)節(jié)點的出隊或阻塞線程被中斷來實現(xiàn),只有前驅(qū)節(jié)點是頭節(jié)點才能嘗試獲取同步狀態(tài),這是為什么?
 a.頭節(jié)點是成功獲取到同步狀態(tài)的節(jié)點,而頭節(jié)點的線程釋放了同步狀態(tài)之后,將會喚醒其后繼節(jié)點,后繼節(jié)點的線程被喚醒后需要檢查自己的前驅(qū)是否是頭節(jié)點;
 b.維護同步隊列的FIFO原則;

? ? ? ?在釋放同步狀態(tài)時,同步器調(diào)用tryRelease()方法釋放同步狀態(tài),并喚醒頭節(jié)點的后繼節(jié)點;

3.共享式同步狀態(tài)獲取與釋放
? ? ? ?通過調(diào)用tryAcquireShared()方法嘗試獲得同步狀態(tài),返回值大于等于0,表示能夠獲取到同步狀態(tài),若失敗則進入自旋過程doAcquireShared(),當前驅(qū)是否為頭節(jié)點時嘗試獲取同步狀態(tài),如果返回值大于等于0,則成功并從自旋過程中退出。
? ? ? ?釋放過程與獨占式的區(qū)別主要在于tryReleaseShared()方法必須通過循環(huán)和CAS確保同步狀態(tài)線程安全釋放
4.獨占式超時獲取同步狀態(tài)
? ? ? ?通過自旋,當前驅(qū)節(jié)點為頭節(jié)點時嘗試獲取同步狀態(tài),失敗則判斷是否超時,沒有則重新設置超時時間,然后讓當前線程等待,超時則直接返回;
? ? ? ?當nanosTimeout小于等于spinForTimeoutThreshold(1000納秒),則不會使該線程進行超時等待,而是進入快速自旋過程;

14.重入鎖

? ? ? ?表示該鎖能夠支持一個線程對資源的重復加鎖,該鎖還支持獲取鎖時的公平和非公平性選擇,如果在絕對時間上,先對鎖進行獲取的請求一定先被滿足,則這個鎖是公平的,反之,是不公平的。
? ? ? ?ReentrantLock通過判斷當前線程是否為獲取鎖的線程來決定操作是否成功,成功獲取鎖的線程再次獲取鎖只是增加了同步狀態(tài)值,在釋放同步狀態(tài)時減少相應的值。

15.公平鎖與非公平鎖

? ? ? ?公平性與否時針對獲取鎖而言,如果一個鎖是公平的,那么鎖的獲取順序應該符合請求的絕對順序,也就是FIFO。
? ? ? ?非公平鎖,只要CAS設置同步狀態(tài)成功,則表示當前線程獲取了鎖,剛釋放鎖的線程重新獲得同步狀態(tài)的幾率非常大。雖然可能造成線程“饑餓”,但極少的線程切換,保證了更大的吞吐量
? ? ? ?公平鎖,在非公平鎖的基礎(chǔ)上,加入了對前驅(qū)節(jié)點的判斷,只有等前驅(qū)節(jié)點獲取并釋放了鎖之后才能獲取鎖。保證了鎖的獲取按照FIFO原則,代價是大量的線程切換。

16.讀寫鎖(ReentrantReadWriteLock)

? ? ? ?讀寫鎖在同一時刻允許多個讀線程訪問,但在寫線程訪問時,所有其他線程都被阻塞;ReentrantReadWriteLock通過一個整形變量的高16位維護讀狀態(tài),低16位維護寫狀態(tài)。
1.寫鎖的獲取與釋放
? ? ? ?獲取寫鎖時,在重入條件之上增加了對讀鎖是否存在的判斷;釋放寫鎖時,每次釋放減少寫狀態(tài)(低16位),當寫狀態(tài)為0表示寫鎖已經(jīng)被成功釋放,并置獲取鎖線程為null。
2.讀鎖的獲取與釋放
? ? ? ?獲取讀鎖時,如果其他線程已經(jīng)獲取寫鎖,則當前線程獲取讀鎖失敗,進入等待狀態(tài);如果當前線程獲取了寫鎖或者寫鎖未被獲取,則當前線程成功獲取讀鎖;先快速獲取,如果失敗則“死循環(huán)”獲取讀鎖。
? ? ? ?釋放讀鎖時,讀狀態(tài)減少1<<16;
3.鎖降級
? ? ? ?鎖降級指寫鎖降級成為讀鎖,擁有寫鎖,再獲取讀鎖,隨后釋放當前擁有的寫鎖。中間獲取讀鎖是為了保證數(shù)據(jù)的可見性,如果當前線程A不獲取讀鎖直接釋放寫鎖,假設此刻有另一個線程B獲取了寫鎖并修改了數(shù)據(jù),那么當前線程A無法感知線程B對數(shù)據(jù)的更新。同理不支持鎖升級。
17.Condition
? ? ? ?每個Condition對象都包含著一個隊列(等待隊列),用于實現(xiàn)等待/通知功能。
1.等待隊列
? ? ? ?等待隊列是一個FIFO隊列,隊列中每個節(jié)點包含一個線程引用,該線程就是在Condition上等待的線程,如果一個線程調(diào)用了Condition.await()方法,該線程會釋放鎖、構(gòu)造成節(jié)點并加入等待隊列進入等待狀態(tài)。
? ? ? ?在Object的監(jiān)視器模型上,一個對象包含一個同步隊列和等待隊列;而并發(fā)包中的Lock擁有一個同步隊列和多個等待隊列。
2.等待
? ? ? ?調(diào)用Condition的await()方法,使當前線程進入等待隊列并釋放鎖,同時線程狀態(tài)變?yōu)榈却隣顟B(tài)。相當于同步隊列的首節(jié)點移動到了等待隊列的尾節(jié)點。
3.通知
? ? ? ?調(diào)用Condition的signal()方法,若當前線程獲取了鎖,將會喚醒在等待隊列中等待時間最長的節(jié)點(首節(jié)點),在喚醒節(jié)點前會將節(jié)點移到同步隊列(隊尾),最后通過LockSupport喚醒節(jié)點中的線程

18.ConcurrentHashMap

? ? ? ?不支持key或value為null;hash值通過key的hash值與其無符號右移16位的值異或并除去符號位得到。
1.為什么要使用ConcurrentHashMap
? ? ? ?在多線程環(huán)境下,使用HashMap進行put操作可能會引起死循環(huán),因為多線程會導致HashMap的Entry鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu);
? ? ? ?Hashtable使用synchronized來保證線程安全,效率低下。
2.ConcurrentHashMap如何實現(xiàn)線程安全性
? ? ? ?在JDK1.7中,ConcurrentHashMap容器通過多把鎖,每一把鎖用于鎖容器的其中一部分數(shù)據(jù),即分段加鎖,當多線程訪問容器的不同數(shù)據(jù)段的數(shù)據(jù)時,線程間就不會存在鎖的競爭,從而有效提高了并發(fā)訪問效率。
? ? ? ?在JDK1.8中,若桶為空則直接通過CAS操作添加節(jié)點,否則synchronized鎖住鏈表的頭節(jié)點或者樹的根節(jié)點后進行添加。

19.并發(fā)工具類

1.CountDownLatch
? ? ? ?運行一個或多個線程等待其他線程完成操作接受一個int類型參數(shù)N(N>=0)作為計數(shù)器,每次調(diào)用countDown方法時N就會減1,調(diào)用await方法會阻塞當前線程,直到N變?yōu)?。
? ? ? ?線程A調(diào)用countDown方法happen-before線程B調(diào)用await方法
2.同步屏障CyclicBarrier
? ? ? ?可循環(huán)使用的屏障,讓一組線程到達一個屏障(同步點)時被阻塞,直到最后一個線程到達屏障,可以用于多線程計算數(shù)據(jù)。
? ? ? ?默認構(gòu)造方法是CyclicBarrier(int parties),參數(shù)表明屏障攔截的線程數(shù)量,每個線程調(diào)用await方法告訴CyclicBarrier我已經(jīng)達到屏障,然后當前線程被阻塞
3.CyclicBarrier和CountDownLatch的區(qū)別

  • CountDownLatch的計數(shù)器只能使用一次,CyclicBarrier可以使用reset()方法重置;
  • CyclicBarrier提供方法來判斷阻塞線程是否被中斷(isBroken),獲取阻塞線程數(shù)量(getNumberWaiting)

4.信號量Semaphore
? ? ? ?用來控制同時訪問特定資源的線程數(shù)量,通過協(xié)調(diào)各個線程,保證合理的使用公共資源。可以用做流量控制,比如數(shù)據(jù)庫連接。
5.Exchanger
? ? ? ?交換者Exchanger用于線程間數(shù)據(jù)交換,提供一個同步點交換彼此數(shù)據(jù)。可以用于遺傳算法、校對工作。
? ? ? ?A線程通過執(zhí)行exchange()方法,并等待B線程也執(zhí)行exchange,兩個線程都到達同步點,可以交換數(shù)據(jù)。可以設置超時等待。

20.線程池

好處:

  • a.降低資源開銷:重復利用已創(chuàng)建的線程降低創(chuàng)建和銷毀造成的開銷;
  • b.提高響應速度:任務到達時,不需要等待線程創(chuàng)建就能立即執(zhí)行;
  • c.提高線程的可管理性:可以進行統(tǒng)一分配、調(diào)度和監(jiān)控;

線程池的處理流程:

  • 線程池判斷核心線程池里的線程是否都在執(zhí)行任務,如果是則進入下一步;
  • 線程池判斷工作隊列是否已經(jīng)滿,沒滿則將任務存入工作隊列,否則進入一下步;
  • 線程池判斷線程池的線程是否都處于工作狀態(tài),沒有則創(chuàng)建一個工作線程來執(zhí)行任務,否則執(zhí)行飽和策略;

ThreadPoolExecutor執(zhí)行executor方法分下面四種情況:

  • 當前運行線程數(shù)少于corePoolSize,則創(chuàng)建新線程來執(zhí)行任務(需全局鎖);
  • 當前運行線程數(shù)大于等于corePoolSize,則將任務加入BlockingQueue;
  • 如果任務無法加入BlockingQueue(隊列已滿),則創(chuàng)建新的線程來處理任務(需全局鎖);
  • 如果創(chuàng)建新線程使當前運行線程超出maximumPoolSize,則任務被拒絕;

線程池的使用:
1)線程池的創(chuàng)建

new ThreadPoolExecutor (corePoolSize, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler)  

主要參數(shù)

  • CorePoolSize(線程池的基本大小):提交任務到線程池時,線程池會創(chuàng)建一個線程來執(zhí)行任務,即使其他空閑的基本線程能夠執(zhí)行新任務也會創(chuàng)建線程,直到需要執(zhí)行的任務數(shù)大于線程池基本大小就不再創(chuàng)建,可以通過調(diào)用prestartAllCoreThreads方法提前創(chuàng)建并啟動所有基本線程;

  • RunnableTaskQueue(任務隊列):用于保存等待執(zhí)行任務的阻塞隊列,可以選擇ArrayBlockingQueueLinkedBlockedQueue,SynchronousQueue, PriorityBlockingQueue;

  • MaximumPoolSize(線程池最大數(shù)量):線程池運行創(chuàng)建的最大數(shù)量。若隊列滿且已創(chuàng)建線程數(shù)小于最大線程數(shù),線程池會再創(chuàng)建新的線程執(zhí)行任務;

  • ThreadFactory:用于設置線程工廠,通過線程工廠給每個創(chuàng)建出來的線程設置有意義的名字;

  • RejectedExecutionHandler(飽和策略):當線程池和任務隊列都滿了,線程池處于飽和狀態(tài),必須采取一種策略處理提交的新任務。
        JDK1.5提供了四種策略:
          - AbortPolicy:直接拋出異常;
          - CallerRunsPolicy:只用調(diào)用者所在線程來運行任務;
          - DisCardOldestPolicy:丟棄隊列里最近的一個任務,并執(zhí)行當前任務;
          - DiscardPolicy:直接丟棄,不做處理;

  • f.KeppAliveTime(線程活動保持時間):線程池的工作線程空閑后,保持存活的時間,如果任務很多,任務執(zhí)行時間短,可以調(diào)大來提供線程存活;

  • g.TimeUnit(線程活動保持時間的單位):DAYS, HOURS, MINUTES等

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。