目錄
1. ConcurrentHashMap的實現——JDK7版本
2. ConcurrentHashMap的實現——JDK8版本
ConcurrentHashMap從JDK1.5開始隨java.util.concurrent包一起引入JDK中,主要爲了解決HashMap線程不安全和Hashtable效率不高的問題。衆所周知,HashMap在多線程編程中是線程不安全的,而Hashtable由於使用了synchronized修飾方法而導致執行效率不高;因此,在concurrent包中,實現了ConcurrentHashMap以使在多線程編程中可以使用一個高性能的線程安全HashMap方案。
而JDK1.7之前的ConcurrentHashMap使用分段鎖機制實現,JDK1.8則使用數組+鏈表+紅黑樹數據結構和CAS原子操作實現ConcurrentHashMap;本文將分別介紹這兩種方式的實現方案及其區別。
1. ConcurrentHashMap的實現——JDK7版本
1.1 分段鎖機制
Hashtable之所以效率低下主要是因爲其實現使用了synchronized關鍵字對put等操作進行加鎖,而synchronized關鍵字加鎖是對整個對象進行加鎖,也就是說在進行put等修改Hash表的操作時,鎖住了整個Hash表,從而使得其表現的效率低下;因此,在JDK1.5~1.7版本,Java使用了分段鎖機制實現ConcurrentHashMap.
簡而言之,ConcurrentHashMap在對象中保存了一個Segment數組,即將整個Hash表劃分爲多個分段;而每個Segment元素,即每個分段則類似於一個Hashtable;這樣,在執行put操作時首先根據hash算法定位到元素屬於哪個Segment,然後對該Segment加鎖即可。因此,ConcurrentHashMap在多線程併發編程中可是實現多線程put操作。接下來,本文將詳細分析JDK1.7版本中ConcurrentHashMap的實現原理。
1.2 ConcurrentHashMap的數據結構
ConcurrentHashMap類結構如上圖所示。由圖可知,在ConcurrentHashMap中,定義了一個Segment<K, V>[]數組來將Hash表實現分段存儲,從而實現分段加鎖;而麼一個Segment元素則與HashMap結構類似,其包含了一個HashEntry數組,用來存儲Key/Value對。Segment繼承了ReetrantLock,表示Segment是一個可重入鎖,因此ConcurrentHashMap通過可重入鎖對每個分段進行加鎖。
1.3 ConcurrentHashMap的初始化
JDK1.7的ConcurrentHashMap的初始化主要分爲兩個部分:一是初始化ConcurrentHashMap,即初始化segments數組、segmentShift段偏移量和segmentMask段掩碼等;然後則是初始化每個segment分段。接下來,我們將分別介紹這兩部分初始化。
ConcurrentHashMap包含多個構造函數,而所有的構造函數最終都調用瞭如下的構造函數:
- public ConcurrentHashMap(int initialCapacity,
- float loadFactor, int concurrencyLevel) {
- if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
- throw new IllegalArgumentException();
- if (concurrencyLevel > MAX_SEGMENTS)
- concurrencyLevel = MAX_SEGMENTS;
- // Find power-of-two sizes best matching arguments
- int sshift = 0;
- int ssize = 1;
- while (ssize < concurrencyLevel) {
- ++sshift;
- ssize <<= 1;
- }
- this.segmentShift = 32 - sshift;
- this.segmentMask = ssize - 1;
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- int c = initialCapacity / ssize;
- if (c * ssize < initialCapacity)
- ++c;
- int cap = MIN_SEGMENT_TABLE_CAPACITY;
- while (cap < c)
- cap <<= 1;
- // create segments and segments[0]
- Segment<K,V> s0 =
- new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
- (HashEntry<K,V>[])new HashEntry[cap]);
- Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
- UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
- this.segments = ss;
- }
由代碼可知,該構造函數需要傳入三個參數:initialCapacity、loadFactor、concurrencyLevel,其中,concurrencyLevel主要用來初始化segments、segmentShift和segmentMask等;而initialCapacity和loadFactor則主要用來初始化每個Segment分段。
1.3.1 初始化ConcurrentHashMap
根據ConcurrentHashMap的構造方法可知,在初始化時創建了兩個中間變量ssize和sshift,它們都是通過concurrencyLevel計算得到的。其中ssize表示了segments數組的長度,爲了能通過按位與的散列算法來定位segments數組的索引,必須保證segments數組的長度是2的N次方,所以在初始化時通過循環計算出一個大於或等於concurrencyLevel的最小的2的N次方值來作爲數組的長度;而sshift表示了計算ssize時進行移位操作的次數。
segmentShift用於定位參與散列運算的位數,其等於32減去sshift,使用32是因爲ConcurrentHashMap的hash()方法返回的最大數是32位的;segmentMask是散列運算的掩碼,等於ssize減去1,所以掩碼的二進制各位都爲1.
因爲ssize的最大長度爲65536,所以segmentShift最大值爲16,segmentMask最大值爲65535. 由於segmentShift和segmentMask與散列運算相關,因此之後還會對此進行分析。
1.3.2 初始化Segment分段
ConcurrentHashMap通過initialCapacity和loadFactor來初始化每個Segment. 在初始化Segment時,也定義了一箇中間變量cap,其等於initialCapacity除以ssize的倍數c,如果c大於1,則取大於等於c的2的N次方,cap表示Segment中HashEntry數組的長度;loadFactor表示了Segment的加載因子,通過cap*loadFactor獲得每個Segment的閾值threshold.
默認情況下,initialCapacity等於16,loadFactor等於0.75,concurrencyLevel等於16.
1.4 定位Segment
由於採用了Segment分段鎖機制實現一個高效的同步,那麼首先則需要通過hash散列算法計算key的hash值,從而定位其所在的Segment. 因此,首先需要了解ConcurrentHashMap中hash()函數的實現。
- private int hash(Object k) {
- int h = hashSeed;
-
- if ((0 != h) && (k instanceof String)) {
- return sun.misc.Hashing.stringHash32((String) k);
- }
-
- h ^= k.hashCode();
-
- // Spread bits to regularize both segment and index locations,
- // using variant of single-word Wang/Jenkins hash.
- h += (h << 15) ^ 0xffffcd7d;
- h ^= (h >>> 10);
- h += (h << 3);
- h ^= (h >>> 6);
- h += (h << 2) + (h << 14);
- return h ^ (h >>> 16);
- }
通過hash()函數可知,首先通過計算一個隨機的hashSeed減少String類型的key值的hash衝突;然後利用Wang/Jenkins hash算法對key的hash值進行再hash計算。通過這兩種方式都是爲了減少散列衝突,從而提高效率。因爲如果散列的質量太差,元素分佈不均,那麼使用Segment分段加鎖也就沒有意義了。
- private Segment<K,V> segmentForHash(int h) {
- long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
- return (Segment<K,V>) UNSAFE.getObjectVolatile(segments, u);
- }
接下來,ConcurrentHashMap通過上述定位函數則可以定位到key所在的Segment分段。
1.5 ConcurrentHashMap的操作
在介紹ConcurrentHashMap的操作之前,首先需要介紹一下Unsafe類,因爲在JDK1.7新版本中是通過Unsafe類的方法實現鎖操作的。Unsafe類是一個保護類,一般應用程序很少用到,但其在一些框架中經常用到,如JDK、Netty、Spring等框架。Unsafe類提供了一些硬件級別的原子操作,其在JDK1.7和JDK1.8中的ConcurrentHashMap都有用到,但其用法卻不同,在此只介紹在JDK1.7中用到的幾個方法:
- arrayBaseOffset(Class class):獲取數組第一個元素的偏移地址。
- arrayIndexScale(Class class):獲取數組中元素的增量地址。
- getObjectVolatile(Object obj, long offset):獲取obj對象中offset偏移地址對應的Object型field屬性值,支持Volatile讀內存語義。
1.5.1 get
JDK1.7的ConcurrentHashMap的get操作是不加鎖的,因爲在每個Segment中定義的HashEntry數組和在每個HashEntry中定義的value和next HashEntry節點都是volatile類型的,volatile類型的變量可以保證其在多線程之間的可見性,因此可以被多個線程同時讀,從而不用加鎖。而其get操作步驟也比較簡單,定位Segment –> 定位HashEntry –> 通過getObjectVolatile()方法獲取指定偏移量上的HashEntry –> 通過循環遍歷鏈表獲取對應值。
定位Segment:(((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE
定位HashEntry:(((tab.length - 1) & h)) << TSHIFT) + TBASE
1.5.2 put
ConcurrentHashMap的put方法就要比get方法複雜的多,其實現源碼如下:
- public V put(K key, V value) {
- Segment<K,V> s;
- if (value == null)
- throw new NullPointerException();
- int hash = hash(key);
- int j = (hash >>> segmentShift) & segmentMask;
- if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
- (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
- s = ensureSegment(j);
- return s.put(key, hash, value, false);
- }
同樣的,put方法首先也會通過hash算法定位到對應的Segment,此時,如果獲取到的Segment爲空,則調用ensureSegment()方法;否則,直接調用查詢到的Segment的put方法插入值,注意此處並沒有用getObjectVolatile()方法讀,而是在ensureSegment()中再用volatile讀操作,這樣可以在查詢segments不爲空的時候避免使用volatile讀,提高效率。在ensureSegment()方法中,首先使用getObjectVolatile()讀取對應Segment,如果還是爲空,則以segments[0]爲原型創建一個Segment對象,並將這個對象設置爲對應的Segment值並返回。
在Segment的put方法中,首先需要調用tryLock()方法獲取鎖,然後通過hash算法定位到對應的HashEntry,然後遍歷整個鏈表,如果查到key值,則直接插入元素即可;而如果沒有查詢到對應的key,則需要調用rehash()方法對Segment中保存的table進行擴容,擴容爲原來的2倍,並在擴容之後插入對應的元素。插入一個key/value對後,需要將統計Segment中元素個數的count屬性加1。最後,插入成功之後,需要使用unLock()釋放鎖。
1.5.3 size
ConcurrentHashMap的size操作的實現方法也非常巧妙,一開始並不對Segment加鎖,而是直接嘗試將所有的Segment元素中的count相加,這樣執行兩次,然後將兩次的結果對比,如果兩次結果相等則直接返回;而如果兩次結果不同,則再將所有Segment加鎖,然後再執行統計得到對應的size值。
2. ConcurrentHashMap的實現——JDK8版本
在JDK1.7之前,ConcurrentHashMap是通過分段鎖機制來實現的,所以其最大併發度受Segment的個數限制。因此,在JDK1.8中,ConcurrentHashMap的實現原理摒棄了這種設計,而是選擇了與HashMap類似的數組+鏈表+紅黑樹的方式實現,而加鎖則採用CAS和synchronized實現。
2.1 CAS原理
一般地,鎖分爲悲觀鎖和樂觀鎖:悲觀鎖認爲對於同一個數據的併發操作,一定是爲發生修改的;而樂觀鎖則任務對於同一個數據的併發操作是不會發生修改的,在更新數據時會採用嘗試更新不斷重試的方式更新數據。
CAS(Compare And Swap,比較交換):CAS有三個操作數,內存值V、預期值A、要修改的新值B,當且僅當A和V相等時纔會將V修改爲B,否則什麼都不做。Java中CAS操作通過JNI本地方法實現,在JVM中程序會根據當前處理器的類型來決定是否爲cmpxchg指令添加lock前綴。如果程序是在多處理器上運行,就爲cmpxchg指令加上lock前綴(Lock Cmpxchg);反之,如果程序是在單處理器上運行,就省略lock前綴。
Intel的手冊對lock前綴的說明如下:
- 確保對內存的讀-改-寫操作原子執行。之前採用鎖定總線的方式,但開銷很大;後來改用緩存鎖定來保證指令執行的原子性。
- 禁止該指令與之前和之後的讀和寫指令重排序。
- 把寫緩衝區中的所有數據刷新到內存中。
CAS同時具有volatile讀和volatile寫的內存語義。
不過CAS操作也存在一些缺點:1. 存在ABA問題,其解決思路是使用版本號;2. 循環時間長,開銷大;3. 只能保證一個共享變量的原子操作。
爲了能更好的利用CAS原理解決併發問題,JDK1.5之後在java.util.concurrent.atomic包下采用CAS實現了一系列的原子操作類,這在之後的文章中會詳細分析介紹。
2.2 ConcurrentHashMap的數據結構
JDK1.8的ConcurrentHashMap數據結構比JDK1.7之前的要簡單的多,其使用的是HashMap一樣的數據結構:數組+鏈表+紅黑樹。ConcurrentHashMap中包含一個table數組,其類型是一個Node數組;而Node是一個繼承自Map.Entry<K, V>的鏈表,而當這個鏈表結構中的數據大於8,則將數據結構升級爲TreeBin類型的紅黑樹結構。另外,JDK1.8中的ConcurrentHashMap中還包含一個重要屬性sizeCtl,其是一個控制標識符,不同的值代表不同的意思:其爲0時,表示hash表還未初始化,而爲正數時這個數值表示初始化或下一次擴容的大小,相當於一個閾值;即如果hash表的實際大小>=sizeCtl,則進行擴容,默認情況下其是當前ConcurrentHashMap容量的0.75倍;而如果sizeCtl爲-1,表示正在進行初始化操作;而爲-N時,則表示有N-1個線程正在進行擴容。
2.3 ConcurrentHashMap的初始化
JDK1.8的ConcurrentHashMap的初始化過程也比較簡單,所有的構造方法最終都會調用如下這個構造方法。
- public ConcurrentHashMap(int initialCapacity,
- float loadFactor, int concurrencyLevel) {
- if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
- throw new IllegalArgumentException();
- if (initialCapacity < concurrencyLevel) // Use at least as many bins
- initialCapacity = concurrencyLevel; // as estimated threads
- long size = (long)(1.0 + (long)initialCapacity / loadFactor);
- int cap = (size >= (long)MAXIMUM_CAPACITY) ?
- MAXIMUM_CAPACITY : tableSizeFor((int)size);
- this.sizeCtl = cap;
- }
該初始化過程通過指定的初始容量initialCapacity,加載因子loadFactor和預估併發度concurrencyLevel三個參數計算table數組的初始大小sizeCtl的值。
可以看到,在構造ConcurrentHashMap時,並不會對hash表(Node<K, V>[] table)進行初始化,hash表的初始化是在插入第一個元素時進行的。在put操作時,如果檢測到table爲空或其長度爲0時,則會調用initTable()方法對table進行初始化操作。
- private final Node<K,V>[] initTable() {
- Node<K,V>[] tab; int sc;
- while ((tab = table) == null || tab.length == 0) {
- if ((sc = sizeCtl) < 0)
- Thread.yield(); // lost initialization race; just spin
- else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
- try {
- if ((tab = table) == null || tab.length == 0) {
- int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
- @SuppressWarnings("unchecked")
- Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
- table = tab = nt;
- sc = n - (n >>> 2);
- }
- } finally {
- sizeCtl = sc;
- }
- break;
- }
- }
- return tab;
- }
可以看到,該方法使用一個循環實現table的初始化;在循環中,首先會判斷sizeCtl的值,如果其小於0,則說明其正在進行初始化或擴容操作,則不執行任何操作,調用yield()方法使當前線程返回等待狀態;而如果sizeCtl大於等於0,則使用CAS操作比較sizeCtl的值是否是-1,如果是-1則進行初始化。初始化時,如果sizeCtl的值爲0,則創建默認容量的table;否則創建大小爲sizeCtl的table;然後重置sizeCtl的值爲0.75n,即當前table容量的0.75倍,並返回創建的table,此時初始化hash表完成。
2.4 Node鏈表和紅黑樹結構轉換
上文中說到,一個table元素會根據其包含的Node節點數在鏈表和紅黑樹兩種結構之間切換,因此我們本節先介紹Node節點的結構轉換的實現。
首先,在table中添加一個元素時,如果添加元素的鏈表節點個數超過8,則會觸發鏈表向紅黑樹結構轉換。具體的實現方法如下:
- private final void treeifyBin(Node<K,V>[] tab, int index) {
- Node<K,V> b; int n, sc;
- if (tab != null) {
- if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
- tryPresize(n << 1);
- else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
- synchronized (b) {
- if (tabAt(tab, index) == b) {
- TreeNode<K,V> hd = null, tl = null;
- for (Node<K,V> e = b; e != null; e = e.next) {
- TreeNode<K,V> p =
- new TreeNode<K,V>(e.hash, e.key, e.val,
- null, null);
- if ((p.prev = tl) == null)
- hd = p;
- else
- tl.next = p;
- tl = p;
- }
- setTabAt(tab, index, new TreeBin<K,V>(hd));
- }
- }
- }
- }
- }
該方法首先會檢查hash表的大小是否大於等於MIN_TREEIFY_CAPACITY,默認值爲64,如果小於該值,則表示不需要轉化爲紅黑樹結構,直接將hash表擴容即可。
如果當前table的長度大於64,則使用CAS獲取指定的Node節點,然後對該節點通過synchronized加鎖,由於只對一個Node節點加鎖,因此該操作並不影響其他Node節點的操作,因此極大的提高了ConcurrentHashMap的併發效率。加鎖之後,便是將這個Node節點所在的鏈表轉換爲TreeBin結構的紅黑樹。
然後,在table中刪除元素時,如果元素所在的紅黑樹節點個數小於6,則會觸發紅黑樹向鏈表結構轉換。具體實現如下:
- static <K,V> Node<K,V> untreeify(Node<K,V> b) {
- Node<K,V> hd = null, tl = null;
- for (Node<K,V> q = b; q != null; q = q.next) {
- Node<K,V> p = new Node<K,V>(q.hash, q.key, q.val, null);
- if (tl == null)
- hd = p;
- else
- tl.next = p;
- tl = p;
- }
- return hd;
- }
該方法實現簡單,在此不再進行細緻分析。
2.5 ConcurrentHashMap的操作
2.5.1 get
通過get獲取hash表中的值時,首先需要獲取key值的hash值。而在JDK1.8的ConcurrentHashMap中通過speed()方法獲取。
- static final int spread(int h) {
- return (h ^ (h >>> 16)) & HASH_BITS;
- }
speed()方法將key的hash值進行再hash,讓hash值的高位也參與hash運算,從而減少哈希衝突。然後再查詢對應的value值。
- public V get(Object key) {
- Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
- int h = spread(key.hashCode());
- if ((tab = table) != null && (n = tab.length) > 0 &&
- (e = tabAt(tab, (n - 1) & h)) != null) {
- if ((eh = e.hash) == h) {
- if ((ek = e.key) == key || (ek != null && key.equals(ek)))
- return e.val;
- }
- else if (eh < 0)
- return (p = e.find(h, key)) != null ? p.val : null;
- while ((e = e.next) != null) {
- if (e.hash == h &&
- ((ek = e.key) == key || (ek != null && key.equals(ek))))
- return e.val;
- }
- }
- return null;
- }
查詢時,首先通過tabAt()方法找到key對應的Node鏈表或紅黑樹,然後遍歷該結構便可以獲取key對應的value值。其中,tabAt()方法主要通過Unsafe類的getObjectVolatile()方法獲取value值,通過volatile讀獲取value值,可以保證value值的可見性,從而保證其是當前最新的值。
2.5.2 put
JDK1.8的ConcurrentHashMap的put操作實現方式主要定義在putVal(K key, V value, boolean onlyIfAbsent)中。
- final V putVal(K key, V value, boolean onlyIfAbsent) {
- if (key == null || value == null) throw new NullPointerException();
- int hash = spread(key.hashCode());
- int binCount = 0;
- for (Node<K,V>[] tab = table;;) {
- Node<K,V> f; int n, i, fh;
- if (tab == null || (n = tab.length) == 0)
- tab = initTable();
- else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
- if (casTabAt(tab, i, null,
- new Node<K,V>(hash, key, value, null)))
- break; // no lock when adding to empty bin
- }
- else if ((fh = f.hash) == MOVED)
- tab = helpTransfer(tab, f);
- else {
- V oldVal = null;
- synchronized (f) {
- if (tabAt(tab, i) == f) {
- if (fh >= 0) {
- binCount = 1;
- for (Node<K,V> e = f;; ++binCount) {
- K ek;
- if (e.hash == hash &&
- ((ek = e.key) == key ||
- (ek != null && key.equals(ek)))) {
- oldVal = e.val;
- if (!onlyIfAbsent)
- e.val = value;
- break;
- }
- Node<K,V> pred = e;
- if ((e = e.next) == null) {
- pred.next = new Node<K,V>(hash, key,
- value, null);
- break;
- }
- }
- }
- else if (f instanceof TreeBin) {
- Node<K,V> p;
- binCount = 2;
- if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
- value)) != null) {
- oldVal = p.val;
- if (!onlyIfAbsent)
- p.val = value;
- }
- }
- }
- }
- if (binCount != 0) {
- if (binCount >= TREEIFY_THRESHOLD)
- treeifyBin(tab, i);
- if (oldVal != null)
- return oldVal;
- break;
- }
- }
- }
- addCount(1L, binCount);
- return null;
- }
put操作大致可分爲以下幾個步驟:
- 計算key的hash值,即調用speed()方法計算hash值;
- 獲取hash值對應的Node節點位置,此時通過一個循環實現。有以下幾種情況:
- 如果table表爲空,則首先進行初始化操作,初始化之後再次進入循環獲取Node節點的位置;
- 如果table不爲空,但沒有找到key對應的Node節點,則直接調用casTabAt()方法插入一個新節點,此時不用加鎖;
- 如果table不爲空,且key對應的Node節點也不爲空,但Node頭結點的hash值爲MOVED(-1),則表示需要擴容,此時調用helpTransfer()方法進行擴容;
- 其他情況下,則直接向Node中插入一個新Node節點,此時需要對這個Node鏈表或紅黑樹通過synchronized加鎖。
- 插入元素後,判斷對應的Node結構是否需要改變結構,如果需要則調用treeifyBin()方法將Node鏈表升級爲紅黑樹結構;
- 最後,調用addCount()方法記錄table中元素的數量。
2.5.3 size
JDK1.8的ConcurrentHashMap中保存元素的個數的記錄方法也有不同,首先在添加和刪除元素時,會通過CAS操作更新ConcurrentHashMap的baseCount屬性值來統計元素個數。但是CAS操作可能會失敗,因此,ConcurrentHashMap又定義了一個CounterCell數組來記錄CAS操作失敗時的元素個數。因此,ConcurrentHashMap中元素的個數則通過如下方式獲得:
元素總數 = baseCount + sum(CounterCell)
- final long sumCount() {
- CounterCell[] as = counterCells; CounterCell a;
- long sum = baseCount;
- if (as != null) {
- for (int i = 0; i < as.length; ++i) {
- if ((a = as[i]) != null)
- sum += a.value;
- }
- }
- return sum;
- }
而JDK1.8中提供了兩種方法獲取ConcurrentHashMap中的元素個數。
- public int size() {
- long n = sumCount();
- return ((n < 0L) ? 0 :
- (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
- (int)n);
- }
-
- public long mappingCount() {
- long n = sumCount();
- return (n < 0L) ? 0L : n; // ignore transient negative values
- }
如代碼所示,size只能獲取int範圍內的ConcurrentHashMap元素個數;而如果hash表中的數據過多,超過了int類型的最大值,則推薦使用mappingCount()方法獲取其元素個數。
以上主要分析了ConcurrentHashMap在JDK1.7和JDK1.8中的兩種不同實現方案,當然ConcurrentHashMap的功能強大,還有很多方法本文都未能詳細解析,但其分析方法與本文以上的內容類似,因此不再贅述,感興趣的同學可以自行分析比較。通過學習JDK源碼,對以後的Java程序設計也有一定的幫助。本系列文章將深入剖析Java concurrent包中的併發編程設計,並從中提煉出一些使用場景,從而爲今後的Java程序設計提供一些小小的靈感。