JUC并發(fā)包是jdk提供的一系列關(guān)于并發(fā)框架的jar包,最基本的有Lock和Condition,對(duì)應(yīng)sychronized和wait¬ify的功能,其核心是AQS抽象隊(duì)列同步器。
AQS
AQS抽象隊(duì)列同步器維護(hù)著一個(gè)FIFO阻塞同步隊(duì)列,還有一個(gè)內(nèi)部類(lèi)ConditionObject,一個(gè)FIFO等待隊(duì)列,它們的基本數(shù)據(jù)結(jié)構(gòu)都是一個(gè)Node類(lèi)。Node主要有prev,next,nextWaiter,waitStatus這些屬性。此外還有一個(gè)state記錄鎖次數(shù),owner記錄當(dāng)前獲取鎖的線程。下面以Reentrantlock來(lái)分析具體過(guò)程。
lock()
首先會(huì)使用cas操作嘗試更新state=1,若成功表明未有線程獲取鎖,設(shè)置當(dāng)前線程為owner,線程繼續(xù)執(zhí)行。若更新失敗表示已有其他線程獲取到鎖,將當(dāng)前線程包裝為一個(gè)Node,并cas設(shè)置waitStatus為-1(Node.Singal)放入AQS隊(duì)列尾部(若head為空,則新建一個(gè)Node節(jié)點(diǎn)作為head),設(shè)置prev和next。然后自旋對(duì)這個(gè)Node嘗試獲取鎖,成功則將此Node設(shè)為head,前一個(gè)節(jié)點(diǎn)的next設(shè)為null幫助gc回收,失敗則park當(dāng)前線程使之阻塞。
unlock()
使state減1,若state為0表明鎖已全部釋放完。獲取AQS結(jié)點(diǎn)中的頭結(jié)點(diǎn)的下一節(jié)點(diǎn),喚醒其中線程,則喚醒此時(shí)被park的線程繼續(xù)自旋獲取到鎖。如果是共享模式則在自旋設(shè)置head之后多執(zhí)行了一步操作,繼續(xù)喚醒后續(xù)共享模式結(jié)點(diǎn)中的線程。
await()
必須在當(dāng)前線程獲取鎖時(shí)才能執(zhí)行,首先把當(dāng)前線程構(gòu)成一個(gè)Node,設(shè)置waitStatus為-2(Node.CONDITION)并放入ConditionObject隊(duì)列尾部。然后對(duì)當(dāng)前線程完全釋放鎖,喚醒AQS中的頭部線程,隨后park當(dāng)前線程。
singal()&singalAll()
取ConditionObject隊(duì)列首部節(jié)點(diǎn),并把它從隊(duì)列中移除,CAS將Node的waitStatus從CONDITION置為0,隨后放入AQS隊(duì)列的尾部,再設(shè)置waitStatus從CONDITION置為-1,unpark喚醒這個(gè)線程。
CAS操作
CAS比較交換是一種樂(lè)觀鎖機(jī)制,其過(guò)程是取當(dāng)前值和預(yù)期值進(jìn)行比較,若一致,則將更新值作為最新值,不一致則操作失敗,它是一種cpu指令級(jí)別的原子操作,且是直接根據(jù)內(nèi)存中的地址來(lái)做對(duì)比更新。
park&unpark
park&unpark底層原理是獲取一個(gè)許可&給予一個(gè)許可,在jvm中每個(gè)線程有一個(gè)Parker實(shí)例,其中有個(gè)_counter變量來(lái)記錄是否有許可。
1.unpark時(shí)先更新_counter為1,并判斷之前的_counter小于1;
如果小于1,則去喚醒阻塞的線程,若有線程被喚醒然后使_counter為0;
如果給定線程沒(méi)有啟動(dòng)start,則該操作沒(méi)有任何效果.
2.park,則會(huì)檢測(cè)_counter是否大于0;
如果大于0則將_counter變成0,線程繼續(xù)執(zhí)行,否則堵塞線程。
3.線程被park后阻塞時(shí),如果被中斷也會(huì)被喚醒。
JUC并發(fā)工具包
1.Semaphore 信號(hào)量是一類(lèi)經(jīng)典的同步工具。信號(hào)量通常用來(lái)限制線程可以同時(shí)訪問(wèn)的(物理或邏輯)資源數(shù)量。
2.CountDownLatch閉鎖 一種非常簡(jiǎn)單、但很常用的同步輔助類(lèi)。其作用是在完成一組正在其他線程中執(zhí)行的操作之前,允許一個(gè)或多個(gè)線程一直阻塞。CountDownLatch閉鎖狀態(tài)包括一個(gè)計(jì)數(shù)器,該計(jì)數(shù)器被初始化為一個(gè)正數(shù),表示需要等待的事件數(shù)量。countDown方法將遞減計(jì)數(shù)器,表示有一個(gè)事件已經(jīng)發(fā)生了,而await方法等待計(jì)數(shù)器到達(dá)零,這表示所有需要的事件都已經(jīng)發(fā)生。如果計(jì)數(shù)器的值為非零,那么await會(huì)一直阻塞直到計(jì)數(shù)器為零,或者等待中的線程中斷,或者等待超時(shí)。
3.CyclicBarrier柵欄類(lèi)似于閉鎖,它能阻塞線程直到某個(gè)事件發(fā)生。當(dāng)線程到達(dá)柵欄位置時(shí),將調(diào)用await方法,這個(gè)方法將阻塞直到所有線程都到達(dá)阻塞位置。如果所有線程都到達(dá)了柵欄的位置,那么柵欄將打開(kāi),此時(shí)所有線程都將被釋放,而柵欄將被重置以便下次使用。如果對(duì)await的調(diào)用超時(shí),或者await阻塞的線程被中斷,那么柵欄就被認(rèn)為是打破了,所有阻塞的await調(diào)用都將終止并拋出BrokenBarrierException。如果成功地通過(guò)柵欄,那么await將為每個(gè)線程返回一個(gè)唯一的到達(dá)索引號(hào),我們可以利用這些索引來(lái)“選舉”產(chǎn)生一個(gè)領(lǐng)導(dǎo)線程,并在下一次迭代中由該領(lǐng)導(dǎo)線程執(zhí)行一些特殊的工作。
柵欄與閉鎖的關(guān)鍵區(qū)別在于:所有線程必須同時(shí)到達(dá)柵欄位置,才能繼續(xù)執(zhí)行。而且閉鎖只能使用一次,而柵欄可以重復(fù)循環(huán)使用。
4.JDK1.7中的Phaser一種可重用的同步屏障,功能上類(lèi)似于CyclicBarrier和CountDownLatch,但使用上更為靈活。CountDownLatch和CyclicBarrier都是只適用于固定數(shù)量的參與者,Phaser適用于可變數(shù)目的屏障。
5.JDK1.8中的StampedLock,優(yōu)化了之前的讀寫(xiě)鎖ReentrantReadWriteLock。讀寫(xiě)鎖特點(diǎn)是讀-讀不互斥(Node設(shè)置為共享模式),讀-寫(xiě)互斥,寫(xiě)-寫(xiě)互斥,本質(zhì)上來(lái)說(shuō)都是悲觀鎖。StampedLock在將讀鎖優(yōu)化成了一種樂(lè)觀鎖機(jī)制,而對(duì)于讀-寫(xiě)的情況,在讀的時(shí)候會(huì)判斷是否有寫(xiě)操作,有則再讀一次。