Java多線程併發08——鎖在Java中的應用

前兩篇文章中,爲各位帶來了,鎖的類型及鎖在Java中的實現。接下來本文將爲各位帶來鎖在Java中的應用相關知識。關注個人公衆號「Java面典」瞭解更多 Java 相關知識點。html

鎖在Java中主要應用仍是在JUC(java.util.concurrent)包下的相關類,經常使用的主要有原子類、原子集合以及阻塞隊列。java

原子類(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() 爲例,其實現原理以下:函數

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() 爲例,其實現原理以下:

public static <U> AtomicLongFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) {
    Class<?> caller = Reflection.getCallerClass();
    if (AtomicLong.VM_SUPPORTS_LONG_CAS)
        return new CASUpdater<U>(tclass, fieldName, caller);
    else
        return new LockedUpdater<U>(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 傳送門

阻塞隊列(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. 一般狀況下爲了保證公平性會下降吞吐量。
// 建立一個公平的阻塞隊列
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中的實現

Java多線程併發06——CAS與AQS

Java多線程併發05——那麼多的鎖你都瞭解了嗎

Java多線程併發04——合理使用線程池

Java多線程併發03——什麼是線程上下文,線程是如何調度的

Java多線程併發02——線程的生命週期與經常使用方法,你都掌握了嗎

Java多線程併發01——線程的建立與終止,你會幾種方式

相關文章
相關標籤/搜索