《java併發程式設計的藝術》併發容器和框架
ConcurrentHashMap
HashMap在併發執行put操作時會引起死迴圈,是因為多執行緒會導致HashMap的Entry列表形成環形資料結構,一旦形成環形資料結構,Entry的next結點永遠不為空,產生死迴圈獲取Entry.
HashTable使用synchronized來保證執行緒安全,因此效率低下。
ConcurrentHashMap使用鎖分段技術:首先將資料分成一段一段儲存,然後給每一段資料配一把鎖,當一個執行緒佔用鎖訪問其中一個段資料的時候,其他段的資料也能被其他執行緒訪問。
結構
ConcurrentHashMap由Segment陣列結構和HashEntry陣列結構組成。Segment是可重入鎖,扮演鎖的角色;HashEntry儲存鍵值對資料。
一個Segment包含一個HashEntry陣列,每個HashEntry是一個連結串列結構的元素,每個Segment守護一個HashEntry陣列裡的元素,當對HashEntry陣列的資料進行修改的時候,必須首先獲得與它對應的Segment鎖。
初始化
ConcurrentHashMap的初始化方法是通過initialCapacity,loadFactor,concurrencyLevel等幾個引數來初始化segment陣列,段偏移量segmentShift,段掩碼segmentMask和每個segment裡的HashEntry陣列來實現。預設情況下concurrencyLevel等於16.
初始化segment:segment陣列的長度ssize是大於或等於concurrentLevel的2的N次方值。
初始化segmentShift和segmentMask:sshift等於ssize從1向左移位的次數。預設情況下等於1需要向左位移4次,所以sshift=4.segmentShift等於32-sshift,segmentMask=ssize-1;
初始化每個segment:同樣的initialCapacity,loadFactor.segment裡HashEntry陣列的長度cap=initialCapacity/ssize*c;c是倍數,如果c>1,取c的2的N次方值。所以cap不是1就是2的N次方。segment的容量threshold = (int)cap x loadFactor.
綜上預設情況下,initialCapacity=16,loadFactor=0.75,concurrencyLevel=16。對應的ssize=16;sshift=4;segmentShift=28;segmentMask=15;cap=1;threshold=0。
定位Segment
分段鎖Segment保護不同段的資料,那麼在插入和獲取ConcurrentHashMap元素的時候,必須先通過雜湊演算法定位到Segment.
ConcurrentHashMap會對元素的hashcode進行二次hash,以減少hash衝突、
操作
- get,get過程不需要加鎖,只有值為空值的時候才加鎖重讀。內部value定義為volatile。
- put,put過程必須加鎖,首先定位到Segment,然後在segment進行插入操作。第一步判斷是否需要對Segment裡的HashEntry陣列進行擴容,第二步定位新增元素的位置,然後將其放到HashEntry陣列裡。
- size,先嚐試2次不鎖住Segment的方式統計各個Segment大小,如果統計過程中count發生了變化,對所有的Segment的put、remove、clean進行加鎖再統計。
ConcurrentLinkedQueue
實現一個執行緒安全的佇列有兩種方式:
- 使用阻塞方法:用一個鎖(入隊和出隊用同一把鎖)或者用兩個鎖(入隊和出隊用不同的鎖)等方式實現。
- 使用非阻塞的方法:使用迴圈CAS。
ConcurrentLinkedQueue是一個基於連結結點的無界執行緒安全佇列,採用FIFO對結點進行排序。
阻塞佇列
阻塞佇列是支援兩個附加操作的佇列,插入and移除方法:
- 插入:佇列滿時不進行插入
- 移除:佇列空時不進行移除
ArrayBlockingQueue
用陣列實現的有界阻塞佇列
LinkedBlockingQueue
用連結串列實現的有界阻塞佇列,預設最大長度為Integer.MAX_VALUE.
PriorityBlockingQueue
支援優先順序的無界阻塞佇列。預設情況下使用自然順序排序,可以自定義類實現compareTo方法或初始化時特定構造引數Comparator。需要注意的是不能保證同優先順序元素的順序。
DelayQueue
支援延時獲取元素的無界阻塞佇列。佇列使用PriorityQueue實現。佇列中的元素必須實現Delayed介面,在建立元素時可以指定多久才能從佇列中獲取當前元素,只有在延遲期滿時才能從佇列中獲取元素。
SynchronousQueue
不儲存元素的阻塞佇列。每一個put必須等待一個take操作,否則不能繼續新增元素。
LinkedTransferQueue
由連結串列結構構成的無界阻塞佇列。實現了tryTransfer和transfer操作。tryTransfer方法是用來試探傳入的元素是否能直接傳給消費者。如果沒有消費者等待接收元素,返回false。transfer等待消費者消費後返回。
LinkedBlockingDeque
LinkedBlockingDeque是一個由連結串列結構組成的雙向阻塞佇列。
Fork/Join框架
將大任務fork成小任務,最後Join各個任務的結果。
ForkJoinTask:
RecursiveAction:用於沒有返回結果的任務。
RecursiveTask:用於有返回結果的任務。
ForkJoinPool:ForkJoinTask要通過ForkJoinPool執行。
舉例,計算1+2+3+4:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class CountTask extends RecursiveTask<Integer> {
public static final int THRESHOLD = 2;
private int start;
private int end;
public CountTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
boolean canCompute = (end-start) <= THRESHOLD;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum += i;
}
}else {
int middle = (start+end)/2;
CountTask leftTask = new CountTask(start, middle);
CountTask rightTask = new CountTask(middle + 1, end);
leftTask.fork();
rightTask.fork();
int leftResult = leftTask.join();
int rightResult = rightTask.join();
sum = leftResult+rightResult;
}
return sum;
}
public static void main(String[] args){
ForkJoinPool forkJoinPool = new ForkJoinPool();
CountTask task = new CountTask(1, 4);
Future<Integer> result = forkJoinPool.submit(task);
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
步驟:
1. 類繼承RecursiveTask
2. 類實現方法compute()
3. 主執行緒呼叫
Fork/Join框架的異常處理:
ForkJoinTask在執行時可能拋異常但是我們在主執行緒無法直接捕獲異常,所以ForkJoinTask提高了isCompletedAbnormally()方法來檢查任務是否已經丟擲異常或已經被取消了,
並且可以通過getException方法獲取異常。
工作竊取演算法
工作竊取演算法是指某個執行緒從其他佇列裡竊取任務來執行。先幹完自己佇列任務的執行緒幫其他執行緒幹活。常用雙端佇列。
相關文章
- 《java併發程式設計的藝術》Executor框架Java程式設計框架
- 《java併發程式設計的藝術》併發工具類Java程式設計
- Java併發程式設計藝術Java程式設計
- Java併發程式設計的藝術,解讀併發程式設計的優缺點Java程式設計
- 《java併發程式設計的藝術》併發底層實現原理Java程式設計
- Java併發程式設計-鎖及併發容器Java程式設計
- 《java併發程式設計的藝術》原子操作類Java程式設計
- Java併發程式設計的藝術(五)——中斷Java程式設計
- 《java併發程式設計的藝術》記憶體模型Java程式設計記憶體模型
- 《java併發程式設計的藝術》執行緒池Java程式設計執行緒
- 【讀書筆記】Java併發程式設計的藝術筆記Java程式設計
- Java 併發程式設計 Executor 框架Java程式設計框架
- 併發程式設計(二)——併發類容器ConcurrentMap程式設計
- 如何評價《Java 併發程式設計藝術》這本書?Java程式設計
- 併發容器與框架——併發容器(二)框架
- java併發程式設計系列:java併發程式設計背景知識Java程式設計
- java 併發程式設計Java程式設計
- Java併發程式設計Java程式設計
- Java同步容器和併發容器Java
- Java併發程式設計——synchronize 和 ReentrantLockJava程式設計ReentrantLock
- Java併發程式設計---java規範與模式下的併發程式設計1.1Java程式設計模式
- Java併發程式設計 - 第十一章 Java併發程式設計實踐Java程式設計
- Java併發程式設計—ThreadLocalJava程式設計thread
- Java併發程式設計:synchronizedJava程式設計synchronized
- Java併發程式設計 -- ThreadLocalJava程式設計thread
- Java併發程式設計 -- ConditionJava程式設計
- Java併發程式設計——ThreadLocalJava程式設計thread
- java-併發程式設計Java程式設計
- Java 併發程式設計解析Java程式設計
- Java併發程式設計-CASJava程式設計
- Java併發程式設計:LockJava程式設計
- 併發程式設計(一)——同步類容器程式設計
- Java併發容器Java
- Java併發(9)- 從同步容器到併發容器Java
- Java併發程式設計ForkJoin的DemoJava程式設計
- 【Java併發程式設計】一、為什麼需要學習併發程式設計?Java程式設計
- Java併發程式設計之synchronizedJava程式設計synchronized
- Java併發程式設計實踐Java程式設計