多線程原理

1、進程 與 線程

A. 進程

在計算機中,咱們把一個任務稱爲一個進程,瀏覽器就是一個進程,視頻播放器是另外一個進程;相似地,音樂播放器和Word都是進程。

B. 線程

某些進程內部還須要同時執行多個子任務,咱們把子任務稱爲線程。

C. 進程 VS 線程

進程和線程是包含關係,可是多任務便可以由多進程實現,也能夠由單進程內的多線程實現,還能夠混合 多進程 \+ 多線程。

多進程的優勢:
穩定性比多線程高,由於在多進程的狀況下,一個進程崩潰不會影響其它進程,而在多線程狀況下,任何一個線程崩潰會直接致使整個進程崩潰。

多進程的缺點:
(1)建立進程比建立線程開銷大,尤爲是在Windows系統上。
(2)進程間通訊比線程間通訊要慢,由於線程間通訊就是讀寫同一個變量,速度很快。

2、多線程

一個Java程序其實是一個JVM進程,JVM進程用一個主線程來執行main()方法,在main()方法內部,咱們又能夠啓動多個線程.
此外,JVM還有負責垃圾回收的其它工做線程等。

和單線程相比,多線程編程的特色在於:多線程常常須要讀寫共享數據,而且須要同步。

A. 線程的建立

Java用Thread對象表示一個線程,經過調用start()啓動一個新線程;一個線程對象只能調用一次start()方法。
線程的執行代碼寫在run()方法中;線程調度由操做系統決定。
Thread.sleep()能夠把當前線程暫停一段時間。
1. 從Thread派生一個自定義類,而後覆寫 run() 方法。
Thread t = new MyThread();
t.start();

class MyThread extends Thread{
    @Override
    public void run(){}
}
2. 建立Thread實例時,傳入一個Runnable實例。
Thread t = new Thread(new MyRunnable());
t.start();

class MyRunnable implements Runnable{
    @Override
    public void run(){}
}

B. 線程的狀態

在Java程序中,一個線程對象只能調用一次start()方法啓動新線程,並在新線程中執行run()方法。
一旦run()方法執行完畢,線程就結束了。
1. Java 線程的狀態有如下幾種
新建(new)
新建立的線程,還沒有執行。
運行(Runable)
運行中的線程,正在執行run()方法的Java代碼。
無限期等待(Waiting)
運行中的線程,由於某些操做在等待中。

沒有設置Timeout參數的Object.wait()方法。
沒有設置Timeout參數的Thread.join()方法。
LookSupport.park()方法。
限期等待(Timed Waiting)
運行中的線程,由於sleep()方法正在計時等待。

Thread.sleep()方法。
設置了Timeout參數的Object.wait()方法。
設置了Timeout參數的Thread.join()方法。
LockSupport.parkNanos()方法。
LockSupport.parkUntil()方法。
阻塞(Blocked)
運行中的線程,由於某些操做被阻塞而掛起。
結束(Terminated)
線程已終止,由於run()方法執行完畢。

C. 線程的方法

Thread.start()
啓動一個線程。
Thread.join()
等待一個線程執行結束。
Thread.interrupt()
中斷線程,經過(isInterrupted)方法判斷此線程是否中斷。
Thread.setDaemon(true)
設置線程爲守護線程,JVM退出時不考慮守護線程。
Thread.setPriority(int n)
設置線程優先級(1~10,默認值5)。
Thread.currentThread()
獲取當前線程。
Synchronized
加鎖、解鎖。

找出修改共享變量的線程代碼塊。
選擇一個共享實例做爲鎖。
使用synchronized(lockObject){…}。

3、線程池

線程池是一種多線程處理形式,處理過程當中將任務添加到隊列,而後在建立線程後自動啓動這些任務;每一個線程都使用默認的堆棧大小,以默認的優先級運行,並處於多線程單元中。**

A. 使用線程池的優點

1.  建立/銷燬 線程伴隨着系統開銷,過於頻繁的建立/銷燬線程,會很大程度上影響系統處理效率;使用線程池能夠下降線程建立/銷燬形成的系統消耗。
2.  提升系統響應速度,當有任務到達時,經過複用已存在的線程,無需等待新線程的建立便能當即執行。
3.  方便線程併發數的管控,由於線程如果無限制的建立,可能會致使內存佔用過多而產生OOM,而且會形成cpu過分切換。

B. 線程池的構造函數

線程池的概念是Executor這個接口,具體實現是 ThreadPoolExecutor 類。
ThreadPoolExecutor 提供了四個構造函數
1. public ThreadPoolExecutor
        (
        int    corePoolSize,
        int    maximumPoolSize,
        long   keepAliveTime,
        TimeUnit   unit,
        BlockingQueue<Runnable>  workQueue
        )

2. public ThreadPoolExecutor
        (
        int    corePoolSize,
        int    maximumPoolSize,
        long  keepAliveTime,
        TimeUnit  unit,
        BlockingQueue<Runnable>  workQueue,
        ThreadFactory  threadFactory
        )

3. public ThreadPoolExecutor
        (
        int    corePoolSize,
        int    maximumPoolSize,
        long  keepAliveTime,
        TimeUnit  unit,
        BlockingQueue<Runnable>  workQueue,
        RejectedExecutionHandler  handler
        )

4. public ThreadPoolExecutor
        (
        int    corePoolSize,
        int    maximumPoolSize,
        long  keepAliveTime,
        TimeUnit  unit,
        BlockingQueue<Runnable>  workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler  handler
        )

C. 構造函數參數詳解

1. int corePoolSize(線程池基本大小)
該線程池中核心線程數最大值。

核心線程:
線程池新建線程的時候,若是當前線程總數小於corePoolSize,則新建的是核心線程。
若是超過corePoolSize,則新建的是非核心線程。

核心線程默認狀況下會一直存活在線程池中,即便這個核心線程啥也不幹(閒置狀態)。
若是指定ThreadPoolExecutor的allowCoreThreadTimeOut這個屬性爲true,那麼核心線程若是處於閒置狀態,超過必定時間,就會被銷燬掉。
2. int maximumPoolSize(線程池最大大小)
線程池所容許的最大線程個數,對於無界隊列(LinkedBlockingQueue),可忽略該參數。

線程總數 = 核心線程數 + 非核心線程數。

非核心線程:
當隊列滿了,且已建立的線程數小於maximumPoolSize,則線程池會建立新的非核心線程來執行任務。
非核心線程,若是閒置的時長超過參數(keepAliveTime)所設定的時長,就會被銷燬。
3. long keepAliveTime(非核心線程的存活保持時間)
當線程池中非核心線程的空閒時間超過了線程存活時間,那麼這個線程就會被銷燬。
4. TimeUnit unit(keepAliveTime的單位)
NANOSECONDS :  1微毫秒 = 1微秒 / 1000
MICROSECONDS: 1微秒 = 1毫秒 / 1000
MILLISECONDS: 1毫秒 = 1秒 / 1000
SECONDS:秒
MINUTES:分
HOURS:小時
DAYS:天
5. BlockingQueue< Runnable > workQueue(任務隊列)
該線程池中的任務隊列,維護着等待執行的Runnable對象。
當全部的核心線程都在幹活時,新添加的任務會被添加到這個隊列中等待處理,若是隊列滿了,則新建非核心線程執行任務。
常見的workQueue類型:
SynchronousQueue
這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它。
若是全部核心線程都在工做,就會新建一個線程來處理這個任務。
爲了保證不出現(線程總數達到了maximumPoolSize而不能新建線程)錯誤,使用這個類型隊列的時候,maximumPoolSize通常指定成Integer.MAX_VALUE。
LinkedBlockingQueue
這個隊列接收到任務的時候,若是當前線程數小於核心線程數,則新建核心線程處理任務。
若是當前線程數等於核心線程數,則進入隊列等待。
因爲這個隊列沒有最大值限制,即全部超過核心線程數的任務都將被添加到隊列中,這也就致使了maximumPoolSize的設定失效。
ArrayBlockingQueue
能夠限定隊列的長度,接收到任務的時候,若是沒有達到corePoolSize的值,則新建核心線程執行任務。
若是達到了,則入隊等候。
若是隊列已滿,則新建非核心線程執行任務。
若是總線程數達到了maximumPoolSize,而且隊列也滿了,則報錯。
DelayQueue
隊列內元素必須實現Delayed接口,這就意味着新添加的任務必須先實現Delayed接口。
這個隊列接收到任務時,首先先入列,只有達到了指定的延時時間,纔會執行任務。
6. ThreadFactory threadFactory(線程工廠)
用於建立新線程:
threadFactory建立的線程也是採用 new Thread() 方式。
threadFactory建立的線程名都具備統一的風格:pool-m-thread-n(m爲線程池的編號,n爲線程池內的線程編號)。
可使用 Thread.currentThread().getName() 查看當前線程。
public Thread new Thread(Runnable r){} 或使用 Executors.defaultThreadFactory()。
7. RejectedExecutionHandler handler(線程飽和策略)
當線程池和隊列都滿了,再加入線程會執行此策略。

ThreadPoolExecutor.AbortPolicy():不執行新任務,直接拋出異常,提示線程池已滿。
ThreadPoolExecutor.DisCardPolicy():不執行新任務,也不拋出異常。
ThreadPoolExecutor.DisCardOldSetPolicy():將消息隊列中的第一個任務替換爲當前新進來的任務執行。
ThreadPoolExecutor.CallerRunsPolicy():直接調用execute來執行當前任務。

D. ThreadPoolExecutor 的執行策略

如上圖所示,當一個任務被添加進線程池時:
首先判斷線程池中是否有線程處於空閒狀態,若是有、則直接執行任務。
若是沒有,則判斷線程數量是否達到corePoolSize,若是沒有、則新建一個線程(核心線程)執行任務。
若是線程數量達到了corePoolSize,則將任務移入隊列等待。
隊列已滿,總線程數未達到maximumPoolSize時,新建線程(非核心線程)執行任務。
隊列已滿,總線程數達到了maximumPoolSize時,則調用handler實現拒絕策略。

E. JAVA 中常見的四種線程池

Java 經過Executors提供了四種線程池,這四種線程池都是直接或間接配置ThreadPoolExecutor的參數實現的。
1. CachedThreadPool()
可緩存線程池。
線程數爲Integer.max_value,也就是無限制大小。
有空閒線程則複用空閒線程,無空閒線程則新建線程。
適用於耗時少,任務量大的狀況。
源碼:
public static ExecutorService newCachedThreadPool(){
    return new ThreadPoolExecutor ( 0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>() );
}
建立方法:
ExecutorService   cachedThreadPool  =  Executors.newCachedThreadPool() ;
2. FixedThreadPool()
定長線程池。
可控制線程最大併發數(同時執行的線程數)。
超出的線程會在隊列中等待。
源碼:
public static ExecutorService newFixedThreadPool(int nThreads){
    return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() );
}
建立方法:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
3. ScheduledThreadPool()
定時及週期性任務執行的線程池。
源碼:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize){
    super ( corePoolSize, Integer.MAX_VALUE, DEFAULT\_KEEPALLIVE\_MILLS, MILLISECONDS, new DelayedWorkQueue() );
}
建立方法:
ExecutorService  scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
執行方法:
1 秒後執行一次任務
    scheduledThreadPool.schedule(new Task, 1, TimeUnit.SECONDS);

2 秒後開始執行定時任務,每3秒執行一次(無論任務須要執行多長時間)
    scheduledThreadPool.scheduledAtFixedRate(new Task, 2, 3, TimeUnit.SECONDS);

2 秒後開始執行定時任務,以3秒爲間隔執行(上一次任務執行完畢後)
    scheduledThreadPool.**scheduledWithFixedDelay**(new Task, 2, 3, TimeUnit.SECONDS);
4. SingleThreadExecutor()
單線程化的線程池。
有且僅有一個工做線程執行任務。
全部任務按照指定順序執行,即遵循隊列的入隊出隊規則。
源碼:
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor ( 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() ));
}
建立方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();

F. 執行與關閉

execute()
執行一個任務,沒有返回值。
submit()
提交一個線程任務,有返回值。
shutdown()
關閉線程池,等待正在執行的任務先完成,而後再關閉。
shutdownNow()
馬上中止正在執行的任務。
awaitTermination()
等待指定的時間讓線程池關閉。

4、Java內存模型

Java 內存模型的主要目標是定義程序中變量的訪問規則,即在虛擬機中將變量存儲到主內存或者將變量從主內存取出這樣的底層細節。

A. 主內存

虛擬機的主內存是虛擬機內存中的一部分。
Java虛擬機規定全部的變量(這裏的變量是指實例字段、靜態字段、構成數組對象的元素,但不包括局部變量和方法參數)必須在主內存中產生。

B. 工做內存

Java虛擬機中每一個線程都有本身的工做內存,該內存是線程私有的。
線程的工做內存保存了線程須要的變量在主內存中的副本。

虛擬機規定,線程對主內存變量的修改必須在線程的工做內存中進行,不能直接讀寫主內存中的變量。
不一樣的線程之間也不能相互訪問對方的工做內存;若是線程之間須要傳遞變量的值,必須經過主內存來做爲中介進行傳遞。

C. Java 虛擬機中主內存和工做內存之間的交互

就是一個變量如何從主內存傳輸到工做內存中,如何把修改後的變量從工做內存同步回主內存。

lock(鎖定)
把一個變量標識爲一條線程獨佔的狀態;做用對象在主內存。
unlock(解鎖)
把一個處於鎖定狀態的變量釋放出來,釋放後纔可被其它線程鎖定;做用對象在主內存。
read(讀取)
把一個變量的值從主內存傳輸到線程工做內存中,以便load操做使用;做用對象在主內存。
load(載入)
把read操做從主內存中獲得的變量值放入工做內存中;做用對象在工做內存。
use(使用)
把工做內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個須要使用到變量值的字節碼指令時將會執行這個操做;做用對象在工做內存。
assign(賦值)
把一個從執行引擎接收到的值賦給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。做用對象在工做內存。
store(存儲)
把工做內存中的一個變量的值傳送到主內存中,以便write操做;做用對象在**工做內存**。
write(寫入)
把store操做從工做內存中獲得的變量的值放入主內存的變量中;做用對象在**主內存**。

D. 以上8種操做的使用規範

Java 內存模型只要求上述操做必須按順序執行,而沒有保證必須是連續執行。

例:變量a、b
將變量a從主內存複製到工做內存,按順序訪問是:read a,   load a 。  
將變量b從工做內存同步回主內存,按順序訪問是:store b,  write b 。
若將a、b從主內存複製到工做內存,也有可能的順序是: read a,  read b,  load b,  load a 。
1. 不容許read和load、store和write操做之一單獨出現
也就是不容許從主內存讀取了變量的值、可是工做內存不接收的狀況,或者是不容許從工做內存將變量的值寫回到主內存、可是主內存不接收的狀況。
2. 不容許一個線程丟棄最近的assign操做
也就是不容許線程在本身的工做線程中修改了變量的值卻不寫回到主內存。
3. 不容許一個線程寫回沒有修改的變量到主內存
也就是若是線程的工做內存中變量沒有發生過任何assign操做,是不容許將該變量的值寫回主內存。
4. 變量只能在主內存中產生
不容許在工做內存中直接使用一個未被初始化的變量,也就是沒有執行load或者assign操做。
5. 一個變量在同一時刻只能被一個線程對其進行lock操做
也就是說一個線程一旦對一個變量加鎖後,在該線程沒有釋放掉鎖以前,其它線程是不能對其加鎖的。
可是同一個線程對一個變量加鎖後,能夠繼續加鎖,同時在釋放鎖的時候釋放鎖次數必須和加鎖次數相同。
6. 對變量執行lock操做,就會清空工做空間該變量的值
執行引擎使用這個變量以前,須要從新load或者assign操做初始化變量的值。
7. 不容許對沒有lock的變量執行unlock操做
若是一個變量沒有被lock操做,那也不能對其執行unlock操做,固然一個線程也不能對被其它線程lock的變量執行unlock操做。
8. 對一個變量執行unlock以前,必須先把變量同步回主內存中
也就是執行store和write操做。

E. 重排序

重排序是指爲了提升指令運行的性能,在編譯時或者運行時對指令執行順序進行調整的機制。
1. 編譯重排序
是指編譯器在編譯源代碼的時候就對代碼執行順序進行分析,在遵循as-if-serial的原則前提下對源碼的執行順序進行調整。
As-if-serial原則
是指在單線程環境下,不管怎麼重排序,代碼的執行結果都是肯定的。
2. 運行時重排序
是指爲了提升執行的運行速度,系統對機器的執行指令的執行順序進行調整。

F. volatile 關鍵字

volatile 是Java虛擬機提供的最輕量級的同步機制。
在訪問volatile變量時不會執行加鎖操做,所以也就不會使執行線程阻塞,也就不能保證操做的原子性。

volatile修飾的變量對全部的線程具備可見性,當一個線程修改了被volatile修飾的變量值時,volatile保證了新值能當即同步到主內存,以及每次使用前當即從主內存刷新。
volatile修飾的變量禁止指令重排序。

volatile 的讀性能消耗與普通變量幾乎相同,可是寫操做稍慢。
由於它須要在本地代碼中插入許多內存屏障指令來保證處理器不發生亂序執行。

5、線程同步

多線程協調運行的原則是:當條件不知足時,線程進入等待狀態;當條件知足時,線程被喚醒,繼續執行任務。**
關鍵字volatile是Java虛擬機提供的最輕量級的同步機制。
volatile保證變量對全部線程的可見性,可是操做並不是原子操做,併發狀況下不安全。
重量級的同步機制使用Synchronize。

A. 併發操做下需注意的特性

1. 原子性(Atomicity)
原子是最小單位,具備不可分割性。

例:a=0;(a非long和double類型)這個操做是不可分割的,那麼咱們說這個操做是原子操做。
例:a++; (a++也能夠寫成:a = a + 1)這個操做是可分割的,因此它不是一個原子操做。

非原子操做都會存在線程安全問題,須要使用sychronized來讓它變成一個原子操做。
Java的concurrent包下提供了一些原子類。
2. 可見性(Visibility)
可見性是指線程之間的可見性,一個線程修改的狀態對另外一個線程是可見的。
也就是一個線程的修改結果,另外一個線程當即就能看到,主要實現方式是修改值後將值同步至主內存。
在Java中,volatile、synchronized、final修飾的變量都具備可見性。

volatile只能讓被它修飾的內容具備可見性,但不能保證它具備原子性。
3. 有序性(Ordering)
若是在線程內部,全部操做都是有序的。
若是在一個線程中觀察另外一個線程,全部操做都是無序的。

volatile 和 synchronized兩個關鍵字能夠保證線程之間操做的有序性。
volatile 是由於其自己包含「禁止指令重排序」的語義。
synchronized是由「一個變量在同一時刻只容許一條線程對其進行lock操做」這條規則得到的。

B. 線程阻塞的代價

1.  若是要阻塞或喚醒一個線程就須要操做系統介入,須要在用戶態與核心態之間切換,這樣的切換會消耗大量的系統資源。
2.  由於用戶態與內核態都有各自專用的內存空間、專用的寄存器等。
3.  用戶態切換至內核態須要傳遞給許多變量、參數給內核。
4.  內核也須要保護好用戶態在切換時的一些寄存器值、變量等,以便內核態調用結束後切換回用戶態繼續工做。

C. 鎖的類型

1. 樂觀鎖
樂觀鎖是一種樂觀思想,即認爲讀多寫少,遇到併發寫的可能性低。
每次去拿數據的時候都認爲別人不會修改,因此不會上鎖。
可是在更新的時候會判斷一下在此期間別人有沒有更新過這個數據。
採起在寫時先讀出當前版本號,而後加鎖操做。
跟上一次的版本號進行比較,若是同樣則進行寫操做,若是不同則要重複 讀~比較~寫 操做。
Java中的樂觀鎖基本都是經過CAS操做實現,CAS是一種更新的原子操做,比較當前值跟傳入值是否同樣,同樣則更新,不然失敗。
2. 悲觀鎖
悲觀鎖就是悲觀思想,即認爲寫多,遇到併發寫的可能性高。
每次去拿數據的時候都認爲別人會修改。
每次在讀寫數據的時候都會上鎖,這樣其它線程想讀寫這個數據就會阻塞直到拿到鎖。
3. 自旋鎖
若是持有鎖的線程能在很短期內釋放鎖資源,那麼等待競爭鎖的線程就不須要作內核態與用戶態之間的切換進入阻塞掛起狀態。
它們只須要等一等(自旋,也就是不釋放CPU),等持有鎖的線程釋放鎖後便可當即獲取鎖,這樣就避免用戶線程和內核之間的切換消耗。

D. JVM 鎖實現原理

1. 重量級Synchronized鎖簡介
Synchronized鎖是存在Java對象頭裏。
2. Synchronized鎖的做用域
Synchronized能夠把任意一個非NULL對象看成鎖。
Synchronized做用於方法時,鎖住的是對象的實例(this)。
Synchronized做用於靜態方法時,鎖住的是class實例。
Synchronized做用於一個對象時,鎖住的是全部以該對象爲鎖的代碼塊。
3. Synchronized工做原理
JVM基於進入和退出 monitor 對象來實現方法同步和代碼塊同步。
代碼塊同步是使用 monitorenter 和 monitorexit 指令實現的。
monitorenter 指令是在編譯後插入到同步代碼塊的開始位置,而 monitorexit 是插入到方法結束處和異常處。
任何對象都有一個monitor與之關聯,當且一個monitor被持有後,它將處於鎖定狀態。
4. JVM鎖的分類及其解釋

無鎖狀態
無鎖即沒有對資源進行鎖定,全部的線程均可以對同一個資源進行訪問,可是隻有一個線程可以成功修改資源。

對象頭開闢25bit的空間用來存儲對象的hashcode,4bit用於存放分代年齡。
1bit用來存放是否偏向鎖的標識位,2bit用來存放鎖標識位爲01。
偏向鎖(Biased Locking)
(-XX:-UseBiasedLocking = false)

偏向鎖會偏向於第一個訪問鎖的線程,若是在運行過程當中,同步鎖只有一個線程訪問.
不存在多線程爭用的狀況,則線程是不須要觸發同步的,這種狀況下,就會給線程加一個偏向鎖。

偏向鎖開闢23bit的空間用來存放線程ID,2bit用來存放epoch。
4bit用於存放分代年齡,1bit用於存放是否偏向鎖標識(0-否,1-是),2bit用來存放鎖標識位爲01。

epoch:
這裏簡單理解,就是epoch的值能夠做爲一種檢測偏向鎖有效性的時間戳。
輕量級鎖
指當前鎖是偏向鎖的時候,被另外的線程訪問,那麼偏向鎖就會升級爲輕量級鎖,其它線程會經過自旋嘗試獲取鎖,不會阻塞,從而提升性能。

輕量級鎖中直接開闢30bit的空間存放指向棧中鎖記錄的指針,2bit存放鎖的標識位,其標識位爲00。
重量級鎖
也就是一般說synchronized的對象鎖,其中指針指向的是monitor對象的起始地址,當一個monitor被某個線程持有後,它便處於鎖定狀態。

開闢30bit的空間存放執行重量級鎖的指針,2bit存放鎖的標識位,其標識位爲10。
GC 標記
GC標記開闢30bit的內存空間卻沒有佔用,2bit存放鎖的標識位,其標識位爲11。
5. synchronized 執行過程
檢測Mark Word裏面是否是當前線程的ID。
若是是,表示當前線程處於偏向鎖。
若是不是,則使用CAS將當前線程的ID替換Mark Word。
若是成功,則表示當前線程得到偏向鎖,置偏向標誌位爲1。
若是失敗,則說明發生競爭,撤銷偏向鎖,進而升級爲輕量級鎖。

當前線程使用CAS將對象頭的Mark Word替換爲鎖記錄指針。
若是成功,當前線程得到鎖。
若是失敗,表示其它線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。
若是自旋成功,則依然處於輕量級狀態。
若是自旋失敗,則升級爲重量級鎖。
6. 加鎖流程
加鎖過程由JVM自身內部實現。
當執行synchronized同步塊的時候,JVM會根據啓用的鎖和當前線程的爭用狀況,決定如何執行同步操做。
在全部線程都啓用的狀況下。
線程進入臨界區時會先去獲取偏向鎖。
若是已經存在偏向鎖了,則會嘗試獲取輕量級鎖,啓用自旋鎖。
若是自旋也沒有獲取到鎖,則使用重量級鎖。
沒有獲取到鎖的線程掛起,直到持有鎖的線程執行完同步塊喚醒它們。

若是線程爭用激烈,應該禁用偏向鎖**(-XX:-UseBiasedLocking = false)**。
加鎖步驟
在代碼進入同步塊的時候,若是同步對象鎖狀態爲無鎖狀態(鎖標誌位爲01狀態,是否爲偏向鎖爲0)。
虛擬機首先在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word拷貝。
拷貝對象頭中的Mark Word複製到鎖記錄中(Lock Record)。

拷貝成功
虛擬機將使用CAS操做嘗試將對象的Mark Word更新爲指向Lock Record的指針。
並將Lock Record裏的owner指針指向對象的Mark Word。
若是這個動做成功了,那麼這個線程就擁有了該對象的鎖。
而且對象Mark Word的鎖標誌位設置爲00,表示此對象處於輕量級鎖狀態。

拷貝失敗
虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀。
若是是就說明當前線程已經擁有了這個對象的鎖,那就能夠直接進入同步塊繼續執行。
不然說明多個線程競爭鎖,輕量級鎖就要膨脹爲重量級鎖,鎖標誌的狀態值變爲10。
Mark Word中存儲的就是指向重量級鎖的指針,後面等待鎖的線程也要進入阻塞狀態。
7. Synchronized 鎖競爭狀態
Java線程在執行到synchronized的時候,會造成兩個字節碼指令,這裏至關因而一個監視器monitor,監控synchronized保護的區域,監視器會設置幾種狀態用來區分請求線程。

ContentionList
競爭隊列,全部請求鎖的線程首先被放在這個競爭隊列中。
EntryList
ContentionList中那些有資格成爲候選資源的線程被移動到EntryList中。
WaitSet
調用wait方法被阻塞的線程被放置在這裏。
OnDeck
任意時刻,最多隻有一個線程正在競爭鎖資源,該線程被稱爲 OnDeck。
Owner
當前已經獲取到鎖資源的線程被稱爲 Owner。
!Owner
當前釋放鎖的線程。
7.1) JVM 每次從隊列的尾部取出一個數據用於鎖競爭候選者(OnDeck)。
可是併發狀況下,ContentionList會被大量的併發線程進行CAS訪問.
爲了下降對尾部元素的競爭,JVM會將一部分線程移動到EntryList中的某個線程置爲OnDeck線程(通常是最早進去的那個線程)。
Owner線程並不直接把鎖傳遞給OnDeck線程,而是把鎖競爭的權利交給OnDeck,OnDeck須要從新競爭鎖。
這樣雖犧牲了一些公平性,可是能極大的提高系統的吞吐量,在JVM中,也把這種選擇行爲稱之爲「競爭切換」。
7.2) OnDeck 線程獲取到鎖資源後會變爲Owner線程。
沒有獲得鎖資源的仍然停留在EntryList中。
若是Owner線程被wait方法阻塞,則轉移到WaitSet隊列中,直到某個時刻經過notify或者notifyAll喚醒,會從新進入EntryList中。
7.3) 處於ContentionList、EntryList、WaitSet中的線程都處於阻塞狀態。
該阻塞是由操做系統來完成的(Linux內核下采用pthread\_mutex\_lock內核函數實現的)。
7.4) Synchronized 是非公平鎖。
synchronized在線程進入ContentionList時。
等待的線程會先嚐試自旋獲取鎖,若是獲取不到就進入ContentionList。
這明顯對於已經進入隊列的線程是不公平的。
還有一個不公平的事情就是自旋獲取鎖的線程還可能直接搶佔OnDeck線程的鎖資源。
8. 鎖優化
減小鎖的時間。
不須要同步執行的代碼,能不放在同步塊裏面執行就不要放在同步塊內執行,可讓鎖儘快釋放。
減小鎖的粒度。
將物理上的一個鎖,拆成邏輯上的多個鎖,增長並行度,從而下降鎖競爭,它的思想也是用空間來換時間。
ConcurrentHashMap
Java中的ConcurrentHashMap使用一個Segment數組。
Segment繼承自ReenTranLock。
每一個Segment就是一個可重入鎖。
每一個Segment都有一個HashEntry<k, v>數據用來存放數據,put操做時,先肯定往哪一個Segment放數據,只須要鎖定這個segment執行put,其它的Segment不會被鎖定。
數組中有多少個Segment就容許同一時刻多少個線程存放數據,這樣增長了併發能力。
LongAdder
實現思路相似ConcurrentHashMap。
LongAdder有一個根據當前併發情況動態改變的Cell數組。
Cell對象裏面有一個long類型的value用來存儲值。
LinkedBlockingQueue
在隊列頭入隊,在隊列尾出隊,入隊和出隊使用不一樣的鎖。
相對於LinkedBlockingArray只有一個鎖效率高。
鎖粗化
大部分狀況下是要讓鎖的粒度最小化,鎖的粗化則是要增大鎖的粒度。

例:假若有一個循環,循環內的操做須要加鎖,咱們應該把鎖放在循環外面,不然每次進出循環,都進出一次臨界區,效率是很是差的。
使用讀寫鎖
CopyOnWriteArrayList, CopyOnWriteArraySet
使用CAS
若是須要同步的操做執行速度很是快,而且線程競爭並不激烈,這時候使用CAS效率會更高。
由於加鎖會致使線程的上下文切換。
若是上下文切換的耗時比同步操做自己更耗時,且線程對資源的競爭不激烈,使用volatiled + CAS操做會是很是高效的選擇。

6、AQS

A. Lock API

1.  void lock():若是鎖可用就得到鎖,若是鎖不可用就阻塞直到鎖釋放。
2.  void lockInterruptibly():和lock()方法類似,但阻塞的線程可中斷,拋出java.lang.InterruptedException異常。
3.  boolean tryLock():非阻塞獲取鎖,嘗試獲取鎖,若是成功返回true。
4.  boolean tryLock(long timeout, TimeUnit timeUnit):帶有超時時間的獲取鎖方法。
5.  void unlock():釋放鎖。

B. 幾種常見的Lock實現

ReentrantLock
ReentrantLock是重入鎖。
是惟一一個實現Lock接口的類。
重入鎖是指線程在得到鎖以後,再次獲取該鎖不須要阻塞,而是直接關聯一次計數器增長重入次數。
ReentrantReadWriteLock
ReentrantReadWriteLock是可重入讀寫鎖。
ReentrantReadWriteLock類中維護了兩個鎖, ReadLock 和 WriteLock,它們都分別實現了Lock接口。
讀寫鎖的基本原則:讀和讀不互斥、讀和寫互斥、寫和寫互斥。
讀寫鎖適用場景:讀多寫少。
StampedLock
StampedLock是JDK8引入的新的鎖機制。
因爲讀寫鎖中 讀和讀能夠併發、但讀和寫是有衝突的,若是大量的讀線程存在,可能會引發寫線程飢餓。
StampedLock是一種樂觀的讀寫鎖策略,使得樂觀鎖徹底不會阻塞寫線程。

C. AQS 概念

AQS
AbstractQueuedSynchronized 的簡稱。
AQS是一個Java提供的底層同步工具類,主要做用是爲Java中的併發同步組件提供統一的底層支持。
AQS用一個int類型的變量表示 當前同步狀態(volatile int state)。
AQS用一個 FIFO線程等待隊列 來存放多線程爭用資源時被阻塞的線程。
state 的訪問方式有三種。
getState(),  setState(),  compareAndSetState()
FIFO 線程等待隊列。
它是一個雙端隊列,遵循FIFO原則。
主要做用是用來存放在鎖上阻塞的線程。
當一個線程嘗試獲取鎖時,若是已經被佔用,那麼當前線程就會構形成一個Node結點。
隊列的頭結點是成功獲取鎖的結點,當頭結點線程釋放鎖時,會喚醒後面的結點並釋放當前頭結點的引用。

D. AQS 的兩種功能

獨佔
獨佔鎖,每次只能有一個線程持有鎖。
例:ReentrantLock
共享
共享鎖,容許多個線程同時獲取鎖,併發訪問共享資源。
例:ReentrantReadWriteLock

E. AQS 源碼詳解

1. 獨佔模式添加結點
waitStatus
waitStatus 表示當前Node結點的等待狀態。

CANCELLED(1):表示當前結點已取消調度,進入該狀態後的結點將不會再變化。
SIGNAL(-1):表示後繼結點在等待當前結點喚醒,後繼結點入隊時,會將前繼結點的狀態更新爲SIGNAL。
CONDITION(-2):表示結點須要等待進入同步隊列,等待獲取同步鎖。
PROPAGATE(-3):共享模式下,前繼結點不只會喚醒其後繼結點,同時也可能會喚醒後繼的後繼結點。
0:新結點入隊時的默認狀態。
acquire(int)
acquire是一種以獨佔方式獲取資源。
若是獲取到資源,線程直接返回。
不然進入等待隊列,直到獲取到資源爲止,且整個過程忽略中斷的影響。
該方法是獨佔模式下線程獲取共享資源的頂層入口。
獲取到資源後,線程就能夠去執行其臨界區的代碼了。

acquire 執行流程:
tryAcquire():嘗試直接去獲取資源,若是成功則直接返回。
addWaiter():將該線程加入等待隊列的尾部,並標記爲獨佔模式。
acquireQueued():使線程在等待隊列中獲取資源,一直獲取到資源後才返回,若是在整個等待過程當中被中斷過,則返回true,不然返回false。

若是線程在等待過程當中被中斷過,它是不響應的,只是獲取資源後才進行自我中斷 selfInterrupt(),將中斷補上。
tryAcquire(int)
tryAcquire嘗試以獨佔的方式獲取資源,若是獲取成功,則直接返回true,不然返回false。
AQS這裏只定義了一個接口,具體資源的獲取交由自定義同步器經過state的get/set/CAS去實現。
至於能不能重入、能不能加塞,那就要看具體的自定義同步器如何設計了。

這裏之因此沒有定義成abstract。
是由於獨佔模式下只用實現tryAcquire – tryRelease。
而共享模式下只用實現 tryAcquireShared – tryReleaseShared。
若是都定義成abstract,那麼每一個模式也要去實現另外一個模式下的接口。

addWaiter(Node)
addWaiter以兩種不一樣的模式構造結點,並返回當前線程所在的結點。
Node.EXCLUSIVE 互斥模式。
Node.SHARED 共享模式。

若是隊列不爲空,則經過compareAndSetTail方法以CAS的方式將當前線程結點加入到等待隊列的末尾。
不然,經過enq(node)方法初始化一個等待隊列,並返回當前結點。

enq(Node)
enq(Node)用於將當前結點插入等待隊列。
若是隊列爲空,則初始化當前隊列,建立一個空的結點做爲head結點,並將tail也指向它。
整個過程以CAS自旋的方式進行,直到成功加入隊尾爲止。

acquireQueued(Node, int)
acquireQueued用於隊列中的線程自旋地以獨佔且不可中斷的方式獲取同步狀態(acquire),直到拿到鎖以後再返回。
結點進入隊尾後,檢查狀態,找到安全休息點。
若是當前結點已經成爲頭結點,嘗試獲取鎖(tryAcquire),若獲取成功則返回。
不然調用park()進入waiting狀態,等待unpark() 或 interrupt() 喚醒本身。
被喚醒後,看本身是否有資格拿到鎖。

若是有資格,head指向當前結點,並返回從入隊到拿到鎖的整個過程當中是否被中斷過。
若是沒有資格,則繼續waiting。

shouldParkAfterFailedAcquire(Node, Node)
shouldParkAfterFailedAcquire方式經過對當前結點的前一個結點的狀態進行判斷,對當前結點作出不一樣的操做。

若前驅結點的狀態已經爲Node.SIGNAL(表示後繼結點在等待前驅結點喚醒),當前結點處於等待被喚醒的狀態。
若前驅結點已取消調度,那就一直往前找,直到找到最近一個正常等待的狀態,並排在它的後面。
若前驅結點非Node.SIGNAL,且沒有取消調度,將前驅結點的狀態置爲Node.SIGNAL。

parkAndCheckInterrupt()
parkAndCheckInterrupt是讓線程去休息,真正進入等待狀態。
調用park(),使線程進入waiting狀態。

兩種途徑能夠喚醒該線程:被unpark(),被interrupt()。
若是被喚醒,查看本身是否是被中斷了。
須要注意的是:Thread.interrupted()會清除當前線程的中斷標記位。

acquire 流程總結
ReentrantLock.lock() 流程就是acquire流程( acquire(1) )。

調用自定義同步器的tryAcquire() 嘗試直接去獲取資源,若是獲取成功則直接返回。
獲取失敗,則調用addWaiter() 將該線程加入等待隊列的尾部,並標記爲獨佔模式(EXCLUSIVE)。
acquireQueued() 使線程在等待隊列中休息,當有機會獲取鎖時(unpark()時)、會去嘗試獲取資源。
獲取到資源後才返回,若是在整個等待過程當中被中斷過,則返回true,不然返回false。
若是線程在等待過程當中被中斷過,它是不響應的,直到獲取資源後才進行自我中斷selfInterrupt(),將中斷補上。

2. 獨佔模式釋放結點
release(int)
release(int) 方法是獨佔模式下線程釋放共享資源的頂層入口。
release(int) 方法會釋放指定量的資源,若是完全釋放了(即state = 0),它會喚醒等待隊列裏的其它線程來獲取資源。

tryRelease(int)
跟tryAcquire() 同樣,tryRelease() 方法是須要獨佔模式的自定義同步器去實現的。
正常來講,tryRelease()都會成功,由於是獨佔模式(EXCLUSIVE)。
該線程釋放資源,那麼它確定已經拿到資源了,直接減去相應量的資源便可(state -= arg)。
也不用考慮線程安全的問題,但要注意它的返回值。

由於release()是根據tryRelease()的返回值來判斷該線程是否已經完成釋放掉資源了。
因此自定義同步器在實現時,若是已經完全釋放掉資源(state=0),要返回true,不然返回false。

unparkSuccessor(Node)
unparkSuccessor方法用於喚醒等待隊列中下一個線程,下一個線程並不必定是當前結點的next結點。
而是下一個能夠用來喚醒的線程,若是這個結點存在,調用unpark()方法喚醒。

3. 共享模式添加結點
acquireShared(int)
acquireShared方法是共享模式下線程獲取資源的頂層入口。
它會獲取指定量的資源,獲取成功則直接返回,獲取失敗則進入等待隊列,直到獲取到資源爲止,整個過程忽略中斷。

tryAcquireShared須要自定義同步器去實現。
AQS已經把其返回值的語義定義好了。

負值表明獲取失敗。
0表明獲取成功,但沒有剩餘資源。
正數表示獲取成功,還有剩餘資源,其它線程還能夠獲取。
acquireShared 流程:
tryAcquireShared() 嘗試獲取資源,成功則直接返回。
若是失敗則經過doAcquireShared() 進入等待隊列park(),直到被unpark()、interrupt()併成功獲取到資源才返回。
整個等待過程也是忽略中斷的。
doAcquireShared(int)
doAcquireShared() 方法是將當前線程加入等待隊列的尾部休息。
直到其它線程釋放資源喚醒本身,本身成功拿到相應量的資源後才返回。

跟獨佔模式相比,共享模式只有線程是head.next時(也就是位於頭結點後面的一個結點),纔會去嘗試獲取資源。
若是資源還有剩餘還會喚醒head.next以後的線程結點。
假如head.next所須要的資源量大於其它線程所釋放的資源量。

則head.next線程不會被喚醒、而是繼續park()等待其它線程釋放資源。
更不會去喚醒它以後的線程結點。
這樣作只是AQS保證嚴格按照入隊順序喚醒,保證公平、但下降了併發效率。

setHeadAndPropagate(Node, int)
setHeadAndPropagate方法跟setHead()比較多了一個步驟。
就是當本身被喚醒的同時,若是還有剩餘資源,還會去喚醒後繼結點。

4. 共享模式釋放資源
releaseShared()
releaseShared()方法是共享模式下線程釋放共享資源的頂層入口。
它會釋放指定量的資源,若是成功釋放則容許喚醒等待線程,它會喚醒等待隊列裏的其它線程來獲取資源。

doReleaseShared()
doReleaseShared方法主要用於喚醒後繼結點。

共享模式釋放資源總結:
releaseShared() 方法跟獨佔模式下的release() 相似。
不一樣之處在於:
獨佔模式下的tryRelease() 在徹底釋放掉資源(state = 0)後,纔會返回true去喚醒其它線程。
共享模式下的releaseShared() 擁有資源的線程在釋放掉部分資源時就能夠喚醒後繼等待結點。

例:資源總量10;有三個線程A、B、C; 其中A須要5個資源,B須要3個資源,C須要4個資源。
A、B兩個線程併發執行共須要8個資源,此時只剩下2個資源,C線程等待。
當A線程執行過程當中釋放了1個資源後,剩餘資源量爲3,C線程仍是繼續等待。
當B線程執行過程當中釋放了1個資源後,剩餘資源量爲4,C線程開始並行執行。

F. 自定義同步器的實現

自定義同步器在實現時只須要實現共享資源state的獲取與釋放便可,其他功能AQS已經在頂層實現好了。**
自定義同步器的主要實現方法。**
isHeldExclusively()
該線程是否正在獨佔資源,只有用到condition才須要去實現它。
tryAcquire(int)
獨佔方式,嘗試獲取資源,成功則返回true,失敗則返回false。
tryRelease(int)
獨佔方式,嘗試釋放資源,成功則返回true,失敗則返回false。
tryAcquireShared(int)
共享方式,嘗試獲取資源。

負數表示失敗。
0表示成功,但沒有剩餘可用資源。
正數表示成功,且有剩餘資源。
tryReleaseShared(int)
共享方式,嘗試釋放資源。
若是釋放後容許喚醒後續等待結點返回true,不然返回false。
ReentrantLock 實現原理。
State初始化爲0,表示未鎖定狀態。
A線程ReentrantLock.lock() 時,調用 tryAcquire() 獨佔鎖並將 state+1。
其它線程再 tryAcquire() 時就會失敗。
直到A線程ReentrantLock.unlock() 到state=0爲止(即釋放鎖),其它線程纔有機會獲取鎖。
A 線程釋放鎖以前,A線程是能夠重複獲取此鎖的(state++),這就是重入鎖的概念。

注意:獲取多少次就要釋放多少次,這樣才能保證state是能回到零態的。

7、CAS

CAS(Compare And Swap),是用於實現多線程同步的原子指令。
CAS將內存位置的內容與給定值進行比較,只有在相同的狀況下,將該內存位置的內容修改成新的給定值。
這是做爲單個原子操做完成的。原子性保證新值基於最新信息計算,若是該值在同一時間被另外一個線程更新,則寫入失敗。
1. CAS 操做步驟
當多個資源同時對某個資源進行CAS操做,只能有一個線程操做成功。
可是並不會阻塞其它線程,其它線程只會收到操做失敗的信號,可見CAS實際上是一個樂觀鎖。

例:假設內存中的原數據是V,舊的預期值是A,須要修改的新值是B。
首先將主內存中的數據V讀取到線程的工做內存中。(讀取)
比較A與V是否相等。(比較)
若是比較相等,將B寫入V。(寫回)
返回操做是否成功。
2. ABA 問題(線程1和線程2同時執行CAS邏輯)
時刻1:線程1執行讀取操做,獲取原值A,而後線程被切換走。
時刻2:線程2執行完成CAS操做,將原值由A修改成B。
時刻3:線程2再次執行CAS操做,並將原值由B修改成A。
時刻4:線程1恢復運行,將比較值與原值進行比較,發現兩個值相等(其實內存中的值已經被線程2修改過了)。
3. 解決ABA問題的方法
在變量前面追加版本號:每次變量更新就把版本號加1,則A-B-A就變成1A-2B-3A。

8、JAVA中經常使用的鎖介紹

公平鎖
公平鎖是指當多個線程嘗試獲取鎖時,成功獲取鎖的順序與請求獲取鎖的順序相同。
非公平鎖
非公平鎖是指當鎖狀態可用時,無論在當前鎖上是否有其它線程在等待,新進來的線程都有機會搶佔鎖。
1. Synchronized
可重入鎖,非公平鎖,由JVM實現。
2. Wait() / notify() / notifyAll()
用於多線程協調運行。
wait():使線程進入等待狀態。
notify():喚醒正在等待的線程。
notifyAll():喚醒全部正在等待的線程。
已喚醒的線程還須要從新得到鎖後才能繼續執行。
3. ReadWriteLock
ReadWriteLock是一種悲觀的讀寫鎖,讀讀能夠同步,讀寫互斥,寫寫互斥。
適用場景:讀多寫少。
4. StampedLock
不可重入鎖,StampedLock是一種樂觀的讀寫鎖,讀的過程當中容許獲取寫鎖後寫入。
5. ReentrantLock
可重入鎖。
基於AQS實現。
提供兩種模式:(1)公平鎖   (2)非公平鎖。
Lock  lock = new ReentrantLock()。

lock.lock();  加鎖。
lock.unlock(); 釋放鎖。
6. ReentrantReadWriteLock
可重入鎖。
基於AQS實現。
實現原理:
將同步變量state按照高16位和低16位進行拆分,高16位表示讀鎖,低16表示寫鎖。
7. Condition
使用condition對象來實現wait() 和notify() 功能。
使用condition時,引用的condition必須從Lock()實例的newCondition返回。

private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
condition.await():釋放當前鎖,進入等待狀態。
condition.signal():喚醒某個等待線程。
condition.signalAll():喚醒全部等待線程。
喚醒線程從await()返回後須要從新得到鎖。
8. Atomic
Atomic類是經過無鎖(lock-free)的方式實現線程的安全(thread-safe)訪問。
Atomic主要原理是利用了CAS來保證線程安全。
9. Future
線程是繼承自Runnable接口,Runnable接口沒有返回值。
若是須要返回值,需實現Callable接口,Callable是一個泛型接口,能夠返回指定類型的結果。

對線程池提交一個Callable任務,能夠得到一個Future對象。
能夠用Future在未來某個時刻獲取結果。
10. CompletableFuture
CompletableFuture是針對Future作的改進,能夠傳入回調對象。
當異步任務完成或者發生異常時,自動調用回調對象的回調方法。
11. ForkJoin
ForkJoin是一種基於「分治」的算法,經過分解任務,並行執行,最後合併結果獲得最終結果。
12. ThreadLocal
ThreadLocal表示線程的「局部變量」,它確保每一個線程的ThreadLocal變量都是各自獨立的。
ThreadLocal適合在一個線程的處理流程中保持上下文(避免了同一個參數在全部方法中傳遞)。
使用ThreadLocal要用try … finally結構,並在finally中清除。

9、JAVA中 Concurrent 集合線程安全類

A. 線程安全類

CopyOnWriteArrayList
寫入時複製。
簡單來講,就是平時查詢的時候,都不須要加鎖,隨便訪問。
只有在寫入/刪除的時候,纔會從原來的數據複製一個副本出來,而後修改這個副本,最後把原數據替換成當前的副本。
修改操做的同時,讀操做不會阻塞,而是繼續讀取舊的數據。
ConcurrentHashMap
設計原理跟HashMap差很少,只是引入了segment的概念。
目的是將map拆分紅多個Segment(默認16個)。
操做ConcurrentHashMap細化到操做某一個segment。
在多線程環境下,不一樣線程操做不一樣的segment,他們互不影響,即可實現併發操做。
CopyOnWriteArraySet
CopyOnWriteArraySet底層存儲結構是**CopyOnWriteArrayList**,是一個線程安全的無序集合。
ArrayBlockingQueue / LinkedBlockingQueue
ArrayBlockingQueue / LinkedBlockingQueue 都是一個阻塞式的隊列。
ArrayBlockingQueue底層是一個數組,ArrayBlockingQueue讀寫共享一個鎖,使用的是ReentrantLock。
LinkedBlockingQueue底層是是鏈表,LinkedBlockingQueue讀寫各有一個鎖,使用的也是ReentrantLock。
LinkedBlockingDeque
LinkedBlockingDeque是一個由鏈表結構組成的雙向阻塞隊列,能夠從隊列的兩端插入和移除元素。
使用全局獨佔鎖保證線程安全,使用的是 ReentrantLock 和 兩個Condition對象(用來阻塞和喚醒線程)。
相關文章
相關標籤/搜索