Java多執行緒併發08——鎖在Java中的應用
阿新 • • 發佈:2020-03-23
>前兩篇文章中,為各位帶來了,鎖的型別及鎖在Java中的實現。接下來本文將為各位帶來鎖在Java中的應用相關知識。關注我的公眾號「Java面典」瞭解更多 Java 相關知識點。
鎖在Java中主要應用還是在JUC(java.util.concurrent)包下的相關類,常用的主要有原子類、原子集合以及阻塞佇列。
# 原子類(Atomicxxx)
## AtomicLong
AtomicInteger、AtomicLong 和 AtomicBoolean 這3個基本型別的原子類的原理和用法相似。
### 作用
**對 Long 進行原子操作。** 在32位作業系統中,64位的 long 和 double 變數由於會被 JVM 當作兩個分離的 32 位來進行操作,所以不具有原子性。而使用 AtomicLong 能讓 long 的操作保持原子型。
### 實現原理
AtomicLong 主要依賴 CAS 原理實現。以 incrementAndGet() 為例,其實現原理如下:
1. incrementAndGet() 首先會根據 get() 獲取 AtomicLong 對應的 long 值;
2. incrementAndGet() 接著將 current 加 1,然後通過 CAS 函式,將新的值賦值給 value。
## AtomicIntegerArray
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray這3個數組型別的原子類的原理和用法相似。
### 作用
AtomicLongArray 的作用則是對"長整形陣列"進行原子操作。
### 實現原理
AtomicLongArray 和 AtomicLong 實現原理類似,也是依賴於 CAS 原理實現。以 addAndGet() 為例,其實現原理如下:
```java
public long addAndGet(int i, long delta) {
// 檢查陣列是否越界
long offset = checkedByteOffset(i);
while (true) {
// 獲取long型陣列的索引 offset 的原始值
long current = getRaw(offset);
// 修改long型值
long next = current + delta;
// 通過CAS更新long型陣列的索引 offset的值。
if (compareAndSetRaw(offset, current, next))
return next;
}
}
```
1. 首先檢查陣列是否越界;
2. 若未越界,採取和 AtomcitLong 一樣的方式進行值更新。
## AtomicReference
### 作用
AtomicReference 是作用是對"物件"進行原子操作。
### 實現原理
AtomicReference 是通過"volatile"和"Unsafe提供的CAS函式實現"原子操作。其實現原理如下:
1. 首先 AtomicReference 類的 value 是 volatile 型別。這保證了:當某執行緒修改 value 的值時,其他執行緒看到的 value 值都是最新的 value 值,即修改之後的 volatile 的值;
2. 通過 CAS 設定 value。這保證了:當某執行緒池通過 CAS 函式(如 compareAndSet 函式)設定 value 時,它的操作是原子的,即執行緒在操作 value 時不會被中斷。
## AtomicIntegerFieldUpdater
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater 這 3 個修改類的成員的原子型別的原理和用法相似。
### 作用
AtomicLongFieldUpdater 可以對指定"類的 'volatile long'型別的成員"進行原子更新。它是基於反射原理實現的。
### 實現原理
以 newUpdater() 為例,其實現原理如下:
```java
public static AtomicLongFieldUpdater newUpdater(Class tclass, String fieldName) {
Class> caller = Reflection.getCallerClass();
if (AtomicLong.VM_SUPPORTS_LONG_CAS)
return new CASUpdater(tclass, fieldName, caller);
else
return new LockedUpdater(tclass, fieldName, caller);
}
```
1. newUpdater() 實際上返回的是CASUpdater物件,或者LockedUpdater物件;
2. 具體返回哪一個類取決於 JVM 是否支援 long 型別的 CAS 函式;
3. CASUpdater 和 LockedUpdater 都是 AtomicIntegerFieldUpdater 的子類,它們的實現類似。
# 原子集合(CopyOnWritexxx、Concurrentxxx)
## CopyOnWriteArrayList
CopyOnWriteArrayList 與 CopyOnWriteArraySet 的作用類似,不過一個是動態陣列,一個是散列表,其實現原理類似。
### 作用
CopyOnWriteArrayList 相當於執行緒安全的 ArrayList 。
### 實現原理
**動態陣列實現**:
1. 在其內部有個“volatile陣列”(array)來儲存資料;
2. 在“新增/修改/刪除”資料時,都會新建一個數組,並將更新後的資料拷貝到新建的陣列中;
3. 最後再將該陣列賦值給“volatile陣列”。
**執行緒安全實現**:
CopyOnWriteArrayList 的執行緒安全是通過 volatile 和互斥鎖來實現的。
1. CopyOnWriteArrayList 是通過“volatile陣列”來儲存資料的。一個執行緒讀取volatile陣列時,總能看到其它執行緒對該 volatile 變數最後的寫入;就這樣,通過 volatile 提供了“讀取到的資料總是最新的”這個機制的保證。
2. CopyOnWriteArrayList 通過互斥鎖來保護資料。在“新增/修改/刪除”資料時,會先“獲取互斥鎖”,再修改完畢之後,先將資料更新到“volatile陣列”中,然後再“釋放互斥鎖”;這樣,就達到了保護資料的目的。
### CopyOnWriteArrayList 與 ArrayList 區別
1. CopyOnWriteArrayList 是執行緒安全的;
2. 因為通常需要複製整個基礎陣列,所以可變操作(add()、set() 和 remove() 等等)的開銷很大;
3. 迭代器支援 hasNext(),next() 等不可變操作,但不支援可變 remove() 等操作;
4. 使用迭代器進行遍歷的速度很快,並且不會與其他執行緒發生衝突。在構造迭代器時,迭代器依賴於不變的陣列快照。
### 適用範圍
CopyOnWriteArrayList 最適合於具有以下特徵的應用程式:
1. List 大小通常保持很小;
2. 只讀操作遠多於可變操作;
3. 需要在遍歷期間防止執行緒間的衝突。
## ConcurrentHashMap
與 ConcurrentHashMap 類似的還有 ConcurrentSkipListMap、ConcurrentSkipListSet。ConcurrentHashMap 的實現原理可以在我往期的文章中檢視。[ConcurrentHashMap 傳送門](https://www.jianshu.com/p/e70edcd4e7d1)
# 阻塞佇列(xxxQueue)
## 阻塞情況
在阻塞佇列中,佇列阻塞有這樣的兩種情況:
1. **當佇列中沒有資料的情況下**,消費者端的所有執行緒都會被自動阻塞(掛起),直到有資料放入佇列;
2. **當佇列中填滿資料的情況下**,生產者端的所有執行緒都會被自動阻塞(掛起),直到佇列中有空的位置,執行緒被自動喚醒。
## 常用方法
方法型別 | 丟擲異常 | 特殊值 | 阻塞 | 超時
:------------: | :----------: | :--------: | :--------: | :------------------------:
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | pull(time, uinit) |
檢查 | element()| peek() | 不可用 | 不可用 |
* 丟擲異常:丟擲一個異常;
* 特殊值:返回一個特殊值(null 或 false,視情況而定);
* 則塞:在成功操作之前,一直阻塞執行緒;
* 超時:放棄前只在最大的時間內阻塞。
## ArrayBlockingQueue(公平、非公平)
**本質**:用陣列實現的有界阻塞佇列。
**特點**
1. 此佇列按照先進先出(FIFO)的原則對元素進行排序;
2. 預設情況下不保證訪問者公平的訪問佇列;
3. 公平訪問佇列是指阻塞的所有生產者執行緒或消費者執行緒,當佇列可用時,可以按照阻塞的先後順序訪問佇列;
4. 通常情況下為了保證公平性會降低吞吐量。
```java
// 建立一個公平的阻塞佇列
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
```
## LinkedBlockingQueue(兩個獨立鎖提高併發)
**本質**:由連結串列結構組成的有界阻塞佇列,與 ArrayListBlockingQueue 類似。
**特點**
1. 此佇列按照先進先出(FIFO)的原則對元素進行排序;
2. 對於生產者端和消費者端分別採用了獨立的鎖來控制資料同步,在高併發的情況下生產者和消費者可以並行地操作佇列中的資料,以此來提高整個佇列的併發效能;
3. LinkedBlockingQueue 會預設一個類似無限大小的容量(Integer.MAX_VALUE)。
## PriorityBlockingQueue(compareTo 排序實現優先)
**本質**:支援優先順序排序的無界阻塞佇列。
**特點**
1. 預設情況下元素採取自然順序升序排列;
2. 可以自定義實現 compareTo() 方法來指定元素進行排序規則,或者初始化 PriorityBlockingQueue 時,指定構造引數 Comparator 來對元素進行排序。
**`PriorityBlockingQueue 不能保證同優先順序元素的順序。`**
## DelayQueue(快取失效、定時任務 )
**本質**:使用優先順序佇列實現的無界阻塞佇列。
**特點**
1. 佇列使用 PriorityQueue 來實現;
2. 佇列中的元素必須實現 Delayed 介面,在建立元素時可以指定多久才能從佇列中獲取當前元素。只有在延遲期滿時才能從佇列中提取元素。
**適用場景**
1. **快取系統的設計**:可以用 DelayQueue 儲存快取元素的有效期,使用一個執行緒迴圈查詢 DelayQueue,一旦能從 DelayQueue 中獲取元素時,表示快取有效期到了。
2. **定時任務排程**:使用 DelayQueue 儲存當天將會執行的任務和執行時間,一旦從 DelayQueue 中獲取到任務就開始執行,從比如 TimerQueue 就是使用 DelayQueue 實現的。
## SynchronousQueue(不儲存資料、可用於傳遞資料)
**本質**:不儲存元素的阻塞佇列。
**特點**
1. 每一個 put 操作必須等待一個 take 操作,否則不能繼續新增元素;
2. 佇列本身並不儲存任何元素,只是負責把生產者執行緒處理的資料直接傳遞給消費者執行緒。
**適用場景**
適合於傳遞性場景,比如在一個執行緒中使用的資料,傳遞給另外一個執行緒使用。
**`SynchronousQueue 的吞吐量高於 LinkedBlockingQueue 和 ArrayBlockingQueue。`**
## LinkedTransferQueue
**本質**:由連結串列結構組成的無界阻塞佇列。
**特點**
相對於其他阻塞佇列,LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。
1. transfer 方法:如果當前有消費者正在等待接收元素(消費者使用 take()方法或帶時間限制的poll()方法時),transfer 方法可以把生產者傳入的元素立刻 transfer(傳輸)給消費者。如果沒有消費者在等待接收元素,transfer 方法會將元素存放在佇列的 tail 節點,並等到該元素被消費者消費了才返回。
2. tryTransfer 方法。則是用來試探下生產者傳入的元素是否能直接傳給消費者。如果沒有消費者等待接收元素,則返回 false。對於帶有時間限制的 tryTransfer(E e, long timeout, TimeUnit unit)方法,則是試圖把生產者傳入的元素直接傳給消費者,但是如果沒有消費者消費該元素則等待指定的時間再返回,如果超時還沒消費元素,則返回 false,如果在超時時間內消費了元素,則返回 true。
**`和 transfer 方法的區別是 tryTransfer 方法無論消費者是否接收,方法立即返回。而 transfer 方法是必須等到消費者消費了才返回。`**
## LinkedBlockingDeque
**本質**:由連結串列結構組成的雙向阻塞佇列。
**特點**
1. 可以從佇列的兩端插入和移出元素;
2. 雙端佇列因為多了一個操作佇列的入口,在多執行緒同時入隊時,也就減少了一半的競爭;
3. 相比其他的阻塞佇列,LinkedBlockingDeque 多了 addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast 等方法。
# 多執行緒與併發系列推薦
**[Java多執行緒併發07——鎖在Java中的實現](https://www.cnblogs.com/weechang/p/12542843.html)**
**[Java多執行緒併發06——CAS與AQS](https://www.cnblogs.com/weechang/p/12545271.html)**
**[Java多執行緒併發05——那麼多的鎖你都瞭解了嗎](https://www.cnblogs.com/weechang/p/12539361.html)**
**[Java多執行緒併發04——合理使用執行緒池](https://www.cnblogs.com/weechang/p/12527425.html)**
**[Java多執行緒併發03——什麼是執行緒上下文,執行緒是如何排程的](https://www.cnblogs.com/weechang/p/12520252.html)**
**[Java多執行緒併發02——執行緒的生命週期與常用方法,你都掌握了嗎](https://www.cnblogs.com/weechang/p/12507989.html)**
**[Java多執行緒併發01——執行緒的建立與終止,你會幾種方式](https://www.cnblogs.com/weechang/p/12499987