這段時間在研究Java
併發相關的內容,一段時間下來算是小有收穫了。ReentrantLock
是Java
併發中的重要部分,因此也是個人首要研究對象,在學習它的過程當中,我發現它是基於抽象隊列同步器AQS實現的,因此我花了點時間學習了一下AQS
的實現原理。這篇博客就來講一說AQS
的做用,以及它是如何實現的。html
AQS
全稱抽象隊列同步器(AbstractQuenedSynchronizer),它是一個能夠用來實現線程同步的基礎框架。固然,它不是咱們理解的Spring
這種框架,它是一個類,類名就是AbstractQuenedSynchronizer
,若是咱們想要實現一個可以完成線程同步的鎖或者相似的同步組件,就能夠在使用AQS
來實現,由於它封裝了線程同步的方式,咱們在本身的類中使用它,就能夠很方便的實現一個咱們本身的鎖。java
AQS
封裝了不少方法,如獲取獨佔鎖,釋放獨佔鎖,獲取共享鎖,釋放共享鎖......咱們能夠經過在本身的實現的同步組件中調用AQS
的這些方法來實現一個線程同步的功能。可是,根據AQS
的名稱也可以想到,咱們不能直接建立AQS
的對象,調用這些方法,由於AQS
是一個抽象類,咱們須要繼承AQS
,建立它的子類對象來使用它。在實際使用中,通常是在咱們本身的類中,之內部類的方式繼承AQS
,而後在內部建立一個對象,在這個類內部使用,好比ReentrantLock
中就是定義了一個抽象內部類Sync
,繼承AQS
,而後定義了一個NonfairSync
類,繼承Sync
,NonfairSync
是一個非公平鎖;同時又定義了一個FairSync
類繼承Sync
,FairSync
是一個公平鎖。node
公平鎖:多個線程按照申請鎖的順序去得到鎖,後申請鎖的線程須要排隊,等它以前的線程得到鎖並釋放後,它才能得到鎖;算法
非公平鎖:線程得到鎖的順序於申請鎖的順序無關,申請鎖的線程能夠直接嘗試得到鎖,誰搶到就是誰的;編程
咱們繼承了AQS
,就能夠直接調用它的方法了嗎?固然不是。Java
中提供的抽象組件,都是幫咱們寫好了通用的部分,可是一些具體的部分,還須要咱們本身實現。舉個比較簡單的例子,Java
中對自定義類型數組的排序,能夠直接調用工具類的sort
方法,sort
方法已經實現了排序的算法,可是其中的比較過程是抽象的,須要咱們本身實現,因此咱們通常須要提供一個比較器(Comparator),或者讓自定義類實現Comparable
接口。這就是模板方法設計模式。設計模式
模板方法:在一個方法中實現了一個算法的流程,可是其中的一些步驟是抽象的,須要在子類中實現,或者具體使用時實現。模板方法能夠提升算法的複用性,提供了算法的彈性,對於不一樣的需求,能夠通用同一份代碼。數組
而AQS
的實現就是封裝了一系列的模板方法,包括獲取鎖、釋放鎖等,這些都是模板方法。這些方法中調用的一些方法並無具體實現,須要使用者根據本身的需求,在子類中進行實現。下面咱們就來看看AQS
中的這些方法。安全
AQS底層維護一個int類型的變量state來表示當前的同步狀態,根據當前state的值,來判斷當前釋放處於鎖定狀態,或者是其餘狀態。而state
的每個值具體是什麼含義,是由咱們本身實現的。咱們繼承AQS
時,根據本身的需求,實現一些方法,其中就是經過修改state
的值來維持同步狀態。而關於state
,主要有如下三個方法:併發
state
的值;state
的值;CAS
設置當前同步狀態的值,方法可以保證設置同步狀態時的原子性;參數expect
爲state
的預期舊值,而update
是須要修改的新值,若設置成功,方法返回true
,不然false
;CAS是一種樂觀鎖,若不瞭解,能夠看看這篇博客:併發——詳細介紹CAS機制框架
接下來咱們再看一看在繼承AQS
時,咱們能夠重寫的方法:
以上這些方法將會在AQS
的模板方法中被調用,咱們根據本身的需求,重寫上述方法,控制同步狀態state
的值,便可控制線程同步的方式。下面再來看看AQS
提供的模板方法:
AQS
提供的模板方法主要分爲三類:
AQS
的同步隊列中正在等待的線程狀況; 下面咱們就來具體說一說AQS
是如何實現線程同步的。
前面提過,AQS
經過一個int
類型的變量state
來記錄當前的同步狀態,也能夠理解爲鎖的狀態,根據state
的值的不一樣,能夠判斷當前鎖是否已經被獲取。就拿獨佔鎖來講,若咱們要實現的是一個獨佔鎖,則鎖被獲取後,其餘線程將沒法獲取鎖,須要進入阻塞狀態,等待鎖被釋放。而線程獲取鎖就是經過修改state
的值來實現的,一個線程修改state
成功,則表示它成功得到了鎖;若失敗,則表示已經有其餘線程得到了鎖,則它須要進入阻塞狀態。下面咱們就來聊一聊AQS
如何實現維持多個線程等待的。
首先說明結論:AQS經過一個同步隊列來維護當前獲取鎖失敗,進入阻塞狀態的線程。這個同步隊列是一個雙向鏈表,獲取鎖失敗的線程會被封裝成一個鏈表節點,加入鏈表的尾部排隊,而AQS
保存了鏈表的頭節點的引用head
以及鏈表的尾節點引用tail
。這個同步隊列以下所示:
在這個同步隊列中,每一個節點對應一個線程,每一個節點都有一個next
指針指向它的下一個節點,以及一個prev
指針指向它的上一個節點。隊列中的頭節點head
就是當前已經獲取了鎖,正在執行的線程對應的節點;而以後的這些節點,則對應着獲取鎖失敗,正在排隊的線程。
當一個線程獲取鎖失敗,它會被封裝成一個Node
,加入同步隊列的尾部排隊,同時線程會進入阻塞狀態。也就是說,在同步隊列中,除了頭節點對應的線程是運行狀態,其他的線程都是等待睡眠狀態。而當頭節點對應的線程釋放鎖時,它會喚醒它的下一個節點(也就是上圖中的第二個節點),被喚醒的節點對應的線程開始嘗試獲取鎖,若獲取成功,它就會將本身置爲head
,而後將原來的head
移出隊列。接下來咱們就經過源碼,具體分析一下AQS
的實現過程。
(1)獲取鎖的實現
AQS
的鎖功能齊全,它既能夠用來實現獨佔鎖,也能夠用來實現共享鎖。
獨佔鎖:也叫排他鎖,即鎖只能由一個線程獲取,若一個線程獲取了鎖,則其餘想要獲取鎖的線程只能等待,直到鎖被釋放。好比說寫鎖,對於寫操做,每次只能由一個線程進行,若多個線程同時進行寫操做,將極可能出現線程安全問題;
共享鎖:鎖能夠由多個線程同時獲取,鎖被獲取一次,則鎖的計數器+1。比較典型的就是讀鎖,讀操做並不會產生反作用,因此能夠容許多個線程同時對數據進行讀操做,而不會有線程安全問題,固然,前提是這個過程當中沒有線程在進行寫操做;
咱們首先分析一下獨佔鎖。在AQS
中,經過方法acquire
來獲取獨佔鎖,acquire
方法的代碼以下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
上面的方法執行流程以下:
首先調用tryAcquire
嘗試獲取一次鎖,若返回true
,表示獲取成功,則acquire
方法將直接返回;若返回false
,則會繼續向後執行acquireQueued
方法;
tryAcquire
返回false
後,將執行acquireQueued
,可是這個方法傳入的參數調用了addWaiter
方法;
addWaiter
方法的做用是將當前線封裝成同步隊列的節點,而後加入到同步隊列的尾部進行排隊,並返回此節點;
addWaiter
方法執行完成後,將它的返回值做爲參數,調用acquireQueued
方法。acquireQueued
方法的做用是讓當前線程在同步隊列中阻塞,而後在被其餘線程喚醒時去獲取鎖;
若線程被喚醒併成功獲取鎖後,將從acquireQueued
方法中退出,同時返回一個boolean
值表示當前線程是否被中斷,若被中斷,則會執行下面的selfInterrupt
方法,響應中斷;
下面咱們就來具體分析這個方法中調用的幾個方法的執行流程。首先第一個tryAcquire
方法:
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
能夠看到,這個方法的實現僅僅只是拋出了一個異常。咱們以前提過,AQS
是基於模板方法設計模式實現的,在其中定義了許多模板方法,在模板方法中會調用一些沒有實現的方法,這些方法須要使用者根據本身的需求實現。而acquire
方法就是一個模板方法,其中調用的tryAcquire
方法就是須要咱們本身實現的方法。tryAcquire
的做用就是嘗試修改state
值,也就是獲取鎖,若修改爲功,則返回true
,不然返回false
。它的實現須要根據AQS
的子類具體分析,好比ReentrantLock
中的Sync
,這裏我就不詳細敘述了,後面寫一篇專門講ReentrantLock
的博客。下面來看看addWaiter
的源碼:
// 將線程封裝成一個節點,放入同步隊列的尾部 private Node addWaiter(Node mode) { // 當前線程封裝成同步隊列的一個節點Node Node node = new Node(Thread.currentThread(), mode); // 這個節點須要插入到原尾節點的後面,因此咱們在這裏先記下原來的尾節點 Node pred = tail; // 判斷尾節點是否爲空,若爲空表示隊列中尚未節點,則不執行如下步驟 if (pred != null) { // 記錄新節點的前一個節點爲原尾節點 node.prev = pred; // 將新節點設置爲新尾節點,使用CAS操做保證了原子性 if (compareAndSetTail(pred, node)) { // 若設置成功,則讓原來的尾節點的next指向新尾節點 pred.next = node; return node; } } // 若以上操做失敗,則調用enq方法繼續嘗試(enq方法見下面) enq(node); return node; } private Node enq(final Node node) { // 使用死循環不斷嘗試 for (;;) { // 記錄原尾節點 Node t = tail; // 若原尾節點爲空,則必須先初始化同步隊列,初始化以後,下一次循環會將新節點加入隊列 if (t == null) { // 使用CAS設置建立一個默認的節點做爲首屆點 if (compareAndSetHead(new Node())) // 首尾指向同一個節點 tail = head; } else { // 如下操做與addWaiter方法中的if語句塊內一致 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
以上就是addWaiter
方法的實現過程,我在代碼中使用註釋對每一步進行了詳細的解析,它的執行過程大體能夠總結爲:將新線程封裝成一個節點,加入到同步隊列的尾部,若同步隊列爲空,則先在其中加入一個默認的節點,再進行加入;若加入失敗,則使用死循環(也叫自旋)不斷嘗試,直到成功爲止。這個過程當中使用CAS
保證了添加節點的原子性。下面看看acquireQueued
方法的源碼:
/** * 讓線程不間斷地獲取鎖,若線程對應的節點不是頭節點的下一個節點,則會進入等待狀態 * @param node the node */ final boolean acquireQueued(final Node node, int arg) { // 記錄失敗標誌 boolean failed = true; try { // 記錄中斷標誌,初始爲true boolean interrupted = false; // 循環執行,由於線程在被喚醒後,可能再次獲取鎖失敗,須要重寫進入等待 for (;;) { // 獲取當前線程節點的前一個節點 final Node p = node.predecessor(); // 若前一個節點是頭節點,則tryAcquire嘗試獲取鎖,若獲取成功,則執行if中的代碼 if (p == head && tryAcquire(arg)) { // 將當前節點設置爲頭節點 setHead(node); // 將原來的頭節點移出同步隊列 p.next = null; // help GC // 失敗標誌置爲false failed = false; // 返回中斷標誌,acquire方法能夠根據返回的中斷標誌,判斷當前線程是否被中斷 return interrupted; } // shouldParkAfterFailedAcquire方法判斷當前線程是否可以進入等待狀態, // 若當前線程的節點不是頭節點的下一個節點,則須要進入等待狀態, // 在此方法內部,當前線程會找到它的前驅節點中,第一個還在正常等待或執行的節點, // 讓其做爲本身的直接前驅,而後在須要時將本身喚醒(由於其中有些線程可能被中斷), // 若找到,則返回true,表示本身能夠進入等待狀態了; // 則繼續調用parkAndCheckInterrupt方法,當前線程在這個方法中等待, // 直到被其餘線程喚醒,或者被中斷後返回,返回時將返回一個boolean值, // 表示這個線程是否被中斷,若爲true,則將執行下面一行代碼,將中斷標誌置爲true if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { // 上面代碼中只有一個return語句,且return的前一句就是failed = false; // 因此只有當異常發生時,failed纔會保持true的狀態運行到此處; // 異常多是線程被中斷,也多是其餘方法中的異常, // 好比咱們本身實現的tryAcquire方法 // 此時將取消線程獲取鎖的動做,將它從同步隊列中移除 if (failed) cancelAcquire(node); } }
以上就是acquireQueued
方法的源碼分析。這個方法的做用能夠歸納爲:讓線程在同步隊列中阻塞,直到它成爲頭節點的下一個節點,被頭節點對應的線程喚醒,而後開始獲取鎖,若獲取成功纔會從方法中返回。這個方法會返回一個boolean
值,表示這個正在同步隊列中的線程是否被中斷。
到此,獲取獨佔鎖的實現就分析完畢了。須要注意的是,這些過程當中使用的compareAndSetXXX
這種形式的方法,都是基於CAS
機制實現的,保證了這些操做的原子性。
(2)釋放鎖的實現
分析完獲取獨佔鎖的代碼後,咱們再來看看釋放鎖的實現。釋放獨佔鎖是經過release
方法實現的:
public final boolean release(int arg) { // 調用tryRelease嘗試修改state釋放鎖,若成功,將返回true,不然false if (tryRelease(arg)) { // 若修改state成功,則表示釋放鎖成功,須要將當前線程移出同步隊列 // 當前線程在同步隊列中的節點就是head,因此此處記錄head Node h = head; // 若head不是null,且waitStatus不爲0,表示它是一個裝有線程的正常節點, // 在以前提到的addWaiter方法中,若同步隊列爲空,則會建立一個默認的節點放入head // 這個默認的節點不包含線程,它的waitStatus就是0,因此不能釋放鎖 if (h != null && h.waitStatus != 0) // 若head是一個正常的節點,則調用unparkSuccessor喚醒它的下一個節點所對應的線程 unparkSuccessor(h); // 釋放成功 return true; } // 釋放鎖失敗 return false; }
以上就是同步隊列中頭節點對應的線程釋放鎖的過程。release
也是一個模板方法,其中經過調用tryRelease
嘗試釋放鎖,而tryRelease
也須要使用者本身實現。在以前也說過,頭節點釋放鎖時,須要喚醒它的下一個節點對應的線程,讓這個線程再也不等待,去獲取鎖,而這個過程就是經過unparkSuccessor
方法實現的。
前面提到過,AQS
不只僅能夠用來實現獨佔鎖,還能夠用來實現共享鎖,下面咱們就來看看AQS
中,有關共享鎖的模板方法的實現。首先是獲取共享鎖的實現,在AQS
中,定義了acquireShared
方法用來獲取共享鎖:
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
能夠看到,這個方法比較簡短。首先調用tryAcquireShared
方法嘗試獲取一次共享鎖,即修改state
的值,若返回值>=0
,則表示獲取成功,線程不受影響,繼續向下執行;若返回值小於0
,表示獲取共享鎖失敗,則線程須要進入到同步隊列中等待,調用doAcquireShared
方法。acquireShared
方法也是AQS
的一個模板方法,而其中的tryAcquireShared
方法就是須要使用者本身實現的方法。下面咱們來看看doAcquireShared
方法的實現:
/** * 不間斷地獲取共享鎖,若線程對應的節點不是頭節點的下一個節點,將進入等待狀態 * 實現與acquireQueued很是相似 * @param arg the acquire argument */ private void doAcquireShared(int arg) { // 往同步隊列的尾部添加一個默認節點,Node.SHARED是一個Node常量, // 它的值就是一個不帶任何參數的Node對象,也就是new Node(); final Node node = addWaiter(Node.SHARED); // 失敗標誌,默認爲true boolean failed = true; try { // 中斷標誌,用來判斷線程在等待的過程當中釋放被中斷 boolean interrupted = false; // 死循環不斷嘗試獲取共享鎖 for (;;) { // 獲取默認節點的前一個節點 final Node p = node.predecessor(); // 判斷當前節點的前一個節點是否爲head節點 if (p == head) { // 嘗試獲取共享鎖 int r = tryAcquireShared(arg); // 若r>0,表示獲取成功 if (r >= 0) { // 當前線程獲取鎖成功後,調用setHeadAndPropagate方法將當前線程設置爲head // 同時,若共享鎖還能被其餘線程獲取,則在這個方法中也會向後傳遞,喚醒後面的線程 setHeadAndPropagate(node, r); // 將原來的head的next置爲null p.next = null; // help GC // 判斷當前線程是否中斷,若被中斷,則調用selfInterrupt方法響應中斷 if (interrupted) selfInterrupt(); // 失敗標誌置爲false failed = false; return; } } // 如下代碼和獲取獨佔鎖的acquireQueued方法相同,即讓當前線程進入等待狀態 // 具體解析能夠看上面acquireQueued方法的解析 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
doAcquireShared
方法的實現和獲取獨佔鎖中的acquireQueued
方法很相似,可是主要有一點不一樣,那就是線程在被喚醒後,若成功獲取到了共享鎖,還須要判斷共享鎖是否還能被其餘線程獲取,若能夠,則繼續向後喚醒它的下一個節點對應的線程。下面再看看釋放共享鎖的代碼,釋放共享鎖時經過方法releaseShared
:
public final boolean releaseShared(int arg) { // 嘗試修改state的值釋放鎖 if (tryReleaseShared(arg)) { // 若成功,則調用如下方法喚醒後繼節點中的線程 doReleaseShared(); return true; } return false; }
releaseShared
也是一個模板方法,它經過調用使用者本身實現的tryReleaseShared
方法嘗試釋放鎖,修改state
的值,若返回true
,表示修改爲功,則繼續向下調用doReleaseShared
喚醒head
的下一個節點對應的線程,讓它開始嘗試獲取鎖;若修改state
失敗,則返回false
。
介紹完上面的內容,下面咱們就來基於AQS
實現一個本身的同步器,或者說鎖。咱們須要實現的鎖要求以下:
實現一個鎖,它是一個共享鎖,可是每次至多支持兩個線程同時獲取鎖,若當前已經有兩個線程獲取了鎖,則其餘獲取鎖的線程須要等待。
實現代碼以下:
/** * 抽象隊列同步器(AQS)使用: * 實現一個同一時刻至多隻支持兩個線程同時執行的同步器 */ // 讓當前類繼承Lock接口 public class TwinLock implements Lock { // 定義鎖容許的最大線程數 private final static int DEFAULT_SYNC_COUNT = 2; // 建立一個鎖對象,用以進行線程同步,Sync繼承自AQS private final Sync sync = new Sync(DEFAULT_SYNC_COUNT); // 之內部類的形式實現一個同步器類,也就是鎖,這個鎖繼承自AQS private static final class Sync extends AbstractQueuedSynchronizer { // 構造方法中指定鎖支持的線程數量 Sync(int count) { // 若count小於0,則默認爲2 if (count <= 0) { count = DEFAULT_SYNC_COUNT; } // 設置初始同步狀態 setState(count); } /** * 重寫tryAcquireShared方法,這個方法用來修改同步狀態state,也就是獲取鎖 */ @Override protected int tryAcquireShared(int arg) { // 循環嘗試 for (; ; ) { // 獲取當前的同步狀態 int nowState = getState(); // 計算當前線程獲取鎖後,新的同步狀態 // 注意這裏使用了減法,由於此時的state表示的是還能支持多少個線程 // 而當前線程若是得到了鎖,則state就要減少 int newState = nowState - arg; // 若是newState小於0,表示當前已經沒有剩餘的資源了 // 則當前線程不能獲取鎖,此時將直接返回小於0的newState; // 或者newState>0,就會執行compareAndSetState方法修改state的值, // 若修改爲功將,將返回大於0的newState; // 若修改失敗,則表示有其餘線程也在嘗試修改state,此時循環一次後,再次嘗試 if (newState < 0 || compareAndSetState(nowState, newState)) { return newState; } } } /** * 嘗試釋放同步狀態 */ @Override protected boolean tryReleaseShared(int arg) { for (; ; ) { // 獲取當前同步狀態 int nowState = getState(); // 計算釋放後的新同步狀態,這裏使用加法, // 表示有線程釋放鎖後,當前鎖能夠支持的線程數量增長了 int newState = nowState + arg; // 使用CAS修改同步狀態,若成功則返回true,不然自旋 if (compareAndSetState(nowState, newState)) { return true; } } } } /** * 獲取鎖的方法 */ @Override public void lock() { // 這裏調用的是AQS的模板方法acquireShared, // 在acquireShared中將調用咱們重寫的tryAcquireShared方法 // 傳入參數爲1表示當前線程,當前線程獲取鎖後,state將-1 sync.acquireShared(1); } /** * 解鎖 */ @Override public void unlock() { // 這裏調用的是AQS的模板方法releaseShared, // 在acquireShared中將調用咱們重寫的tryReleaseShared方法 // 傳入參數爲1表示當前線程,當前線程釋放鎖後,state將+1 sync.releaseShared(1); } /*******************其餘須要實現的方法省略***************************/ }
以上就實現了一個支持兩個線程同時容許的共享鎖,下面咱們經過一個測試代碼來測試效果:
public static void main(String[] args) throws InterruptedException { // 建立一個咱們自定義的鎖對象 Lock lock = new TwinLock(); // 啓動10個線程去嘗試獲取鎖 for (int i = 0; i < 10; i++) { Thread t = new Thread(()->{ // 循環執行 while (true) { // 獲取鎖 lock.lock(); try { // 休眠1秒 Thread.sleep(1000); // 輸出線程名稱 System.out.println(Thread.currentThread().getName()); // 再次休眠一秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 釋放鎖 lock.unlock(); } } }); // 將線程設置爲守護線程,主線程結束後,收穫線程自動結束 t.setDaemon(true); t.start(); } // 主線程每隔1秒輸出一個分割行 for (int i = 0; i < 10; i++) { Thread.sleep(1000); System.out.println("********************************"); } }
以上測試代碼運行後,在每兩個分割行之間,最多不會輸出超過兩個線程的名稱,線程名稱的輸出將會以兩個一隊出現。個人輸出結果以下:
******************************** Thread-1 Thread-0 ******************************** ******************************** Thread-2 Thread-1 ******************************** ******************************** Thread-2 Thread-1 ******************************** ******************************** Thread-2 Thread-3 ******************************** ******************************** Thread-3 Thread-4 ********************************
在研究AQS
的過程當中,我一直有這個疑惑——AQS
如何讓線程阻塞,直到最後才知道有一個叫LockSupport
的工具類。這個工具類定義了不少靜態方法,當須要讓一個阻塞,或者喚醒一個線程時,就能夠調用這個類中的方法,它的底層實現是經過一個sun.misc.Unsafe
類的對象,unsafe
類的方法都是本地方法,由其餘語言實現,這個類是給不支持地址操做的Java
,提供的一個操做內存地址的後門。
AQS
中經過如下兩個方法來阻塞和喚醒線程:
前面講解AQS
的代碼中,用到了方法unparkSuccessor
,它的主要做用就是喚醒當前節點的下一個節點對應的線程,咱們能夠看看它的部分實現:
private void unparkSuccessor(Node node) { // ...........省略其餘代碼............ // 如下代碼即爲喚醒當前節點的下一個節點對應的線程 Node s = node.next; if (s != null) LockSupport.unpark(s.thread); // 使用LockSupport }
其實AQS
還支持一些其餘的方法,好比說在獲取鎖時設置超時時間等,這些方法的實現與上面介紹的幾種大同小異,限於篇幅,這裏就不進行敘述了。以上內容對AQS
的實現原理以及主要方法的實現作了一個比較細緻的介紹,相信看完以後會對AQS
有一個比較深刻的理解,可是想要理解以上內容,須要具有併發的一些基礎知識,好比說線程的狀態,CAS
機制等。最後但願這篇博客對須要的人有所幫助吧。