1. 程式人生 > >Java多執行緒併發08——鎖在Java中的應用

Java多執行緒併發08——鎖在Java中的應用

>前兩篇文章中,為各位帶來了,鎖的型別及鎖在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