閱讀243 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Java中的基礎構建模塊(第五章)

Java中的基礎構建模塊

Java平台類庫包含了豐富的並發基礎構建模塊,例如線程安全的容器類以及各種用於協調多個相互協作的線程控製流的同步工具類。

1.同步容器類

同步容器類都是線程安全的,但在某些情況下可能需要額外的客戶端加鎖來保護複合操作。常見的複合操作包括:迭代、跳轉(在容器內元素之間)、條件運算(例如“若沒有則添加”)。

隱式迭代:某些情況下迭代操作會隱藏起來。如下代碼中println調用Set的toString方法,然後對Set中的對象進行迭代調用toString方法:

public class HiddenIterator {
    private final Set<Integer> set = new HashSet<Integer>();
    public void addTenThings() {
        Random r = new Random();
        for (int i = 0;i < 10;i++) add(r.nextInt());
        System.out.println("Debug : " + set);
    }
}

2.並發容器類

並發容器專門針對多個線程並發訪問設計。
通過使用並發容器代替同步容器,可以極大提高伸縮性並降低風險。
1. ConcurrentHashMap:替代同步的基於散列Map,在其接口中增加了一些常見符合操作的支持,如“若沒有則添加”、替換有條件刪除等。
ConcurrentHashMap使用了一種粒度更細的加鎖機製:分段鎖來實現更大程度的共享,能夠在並發環境下提高吞吐量。
ConcurrentHashMap不能被加鎖來執行獨占訪問。
在實際使用中,隻有當應用程序需要加鎖以進行獨占訪問時,才應該放棄使用ConcurrentHashMap。

ConcurrentHashMap中的原子操作:

方法 說明
v putIfAbsent(K key,V value) 僅當k沒有相應的映射值時才插入
boolean remove(K key,V value) 僅當k被映射到v時才移除
boolean replace(K key,V oldValue,V newValue) 僅當k被映射到oldValue時才進行替換
V replace(K key,V newValue) 僅當k被映射到某個值時才進行替換
  1. CopyOnWriteArrayLIst:代替同步List,提供了更好的並發性能,並且在迭代期間不需要對容器進行加鎖或複製。
    “寫入時複製(Copy-On-Write)”容器的線程安全性在於,隻要正確地發布一個事實不可變對象,那麼在訪問該對象時就不需要進一步的控製,在每次修改時,都會創建並發布一個新的容器副本,從而實現可變性。
    僅當迭代操作遠遠多於修改操作時,才應該使用“寫入時複製”容器。

  2. BlockingQueue(生產者----消費者):相對於Queue,增加了可阻塞的插入和獲取等操作。如果隊列為空,那麼獲取元素的操作將一直阻塞,直到隊列中出現一個可用的元素,如果隊列已滿(對於有界隊列),那麼插入操作將會一直阻塞,直到隊列中出現可用空間。
    阻塞隊列提供了可阻塞的take和put方法,以及支持定時的offer和poll方法。
    在構建高可靠的應用程序時,有界隊列是一種強大的資源管理工具:它們能抑製並產生過多的工作項,使應用程序在負荷過載的情況下變得更加健壯。所以應該通過阻塞隊列在設計中構建資源管理機製。

- LinkedBlockingQueue
- ArrayBlockingQueue
- PriorityBlockingQueue

串行線程封閉:線程封閉對象隻能由一個線程擁有,但可以通過安全地發布該對象來“轉移”所有權,在轉移所有權後,也隻有另一個線程能獲得這個對象的訪問權限,並且發布對象的線程不會再訪問它。

  1. 雙端隊列 Deque---->BlockingDeque---->ArrayBlockingQueue、LinkedBlockingQueue

3.阻塞與中斷

線程阻塞的原因:
- 等待I/O操作結束
- 等待獲得一個鎖
- 等待從sleep方法醒來
- 等待另一個線程的計算結果

Thread.interrupt()用於中斷線程。Thread.interrupt()方法不會中斷一個正在運行的線程。這一方法實際上完成的是,在線程受到阻塞時拋出一個中斷信號,這樣線程就得以退出阻塞的狀態。更確切的說,如果線程被Object.wait, Thread.join和 Thread.sleep三種方法之一阻塞,那麼,它將接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態。

因此,如果線程被上述幾種方法阻塞,正確的停止線程方式是設置共享變量,並調用interrupt()(注意變量應該先設置)。如果線程沒有被阻塞,這時調用interrupt()將不起作用;否則,線程就將得到異常(該線程必須事先預備好處理此狀況),接著逃離阻塞狀態。在任何一種情況中,最後線程都將檢查共享變量然後再停止。

4.同步工具類

同步工具類可以是任何一個對象,隻要它根據其自身的狀態來協調線程的控製流。阻塞隊列可以作為同步工具類,其他類型的同步工具類還包括信號量(Semaphore)柵欄(Barrier)以及閉鎖(Latch)

  • 閉鎖(Latch):閉鎖是一種同步工具類,可以延遲線程的進度直到其到達終止狀態。閉鎖的作用相當於一扇門:在閉鎖到達結束狀態之前,這扇門一直是關閉的,並且沒有任何線程能夠通過,當到達結束狀態時,這扇門會打開並允許所有的線程通過。當閉鎖到達結束狀態後,將不會再改變狀態,因此這扇門將永遠保持打開狀態。 閉鎖可以用來確保某些活動直到其他活動都完成後才繼續執行,如:
    1. 確保某個計算在其需要的所有資源都被初始化之後才繼續執行
    2. 確保某個服務在其依賴的所有其他服務都已經啟動之後才啟動
    3. 等待直到某個操作的所有參與者(如在多玩家遊戲中的所有玩家)都就緒再繼續執行。

Java提供了閉鎖的實現:CountDownLatch。它可以使一個或多個線程等待一組時間發生。閉鎖狀態包括一個計數器,該計數器被初始化為一個正數,表示需要等待的事件數量。countDown()方法遞減計數器,表示有一個時間已經發生了;await()方法等待計數器到達0,表示所有需要等待的事件都已經發生,如果計數器非0,await會一直阻塞直到計數器為0,或等待中的線程中斷、超時。

  • FutureTask
    FutureTask實現了Future語義,表示一種抽象的可生成結果的計算。Future.get的行為取決於任務的狀態,如果任務已經完成,那麼get會立即返回結果,否則get將阻塞直到任務進入完成狀態,然後返回結果或者拋出異常。
    FutureTask表示的計算是通過Callable來實現的,相當於一種可生成結果的Runnable,並且可以處於以下3種狀態:等待運行(Waiting to run)、正在運行(Running)和運行完成(Completed)。
    關於FutureTask的詳細信息可以參看blog:
    Java中創建線程的方法

  • 信號量
    計數信號量(Counting Semaphore)用來控製同時訪問某個特定資源的操作數量,或者同時執行某個指定操作的數量,計數信號量還可以用來實現某種資源池,或者對容器施加邊界。

  • 柵欄
    柵欄類似於閉鎖,它能則色一組線程直到某個事件發生。柵欄與閉鎖的關鍵區別在於:所有線程必須同時到達柵欄位置,才能繼續執行。閉鎖用於等待事件,而柵欄用於等待其他線程。如果所有線程都達到了柵欄位置,那麼柵欄將打開,此時所有的線程都被釋放,而柵欄將被重置以便下次使用。
    Java中提供CyclicBarrier實現柵欄功能。

最後更新:2017-11-04 18:03:44

  上一篇:go  任務執行(第六章)
  下一篇:go  對象的組合(第四章)