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

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——線程的創建與終止,你會幾種方式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章