原文地址:http://www.importnew.com/21288.html
在高併發Java(2):多線程基礎中,我們已經初步提到了基本的線程同步操作。這次要提到的是在併發包中的同步控制工具。
1. 各種同步控制工具的使用
1.1 ReentrantLock
ReentrantLock感覺上是synchronized的增強版,synchronized的特點是使用簡單,一切交給JVM去處理,但是功能上是比較薄弱的。在JDK1.5之前,ReentrantLock的性能要好於synchronized,由於對JVM進行了優化,現在的JDK版本中,兩者性能是不相上下的。如果是簡單的實現,不要刻意去使用ReentrantLock。
相比於synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。
首先我們通過一個例子來說明ReentrantLock最初步的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package
test; import
java.util.concurrent.locks.ReentrantLock; public
class
Test implements
Runnable { public
static
ReentrantLock lock = new
ReentrantLock(); public
static
int
i = 0 ; @Override public
void
run() { for
( int
j = 0 ;
j < 10000000 ;
j++) { lock.lock(); try { i++; } finally { lock.unlock(); } } } public
static
void
main(String[] args) throws
InterruptedException { Test
test = new
Test(); Thread
t1 = new
Thread(test); Thread
t2 = new
Thread(test); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } } |
有兩個線程都對i進行++操作,爲了保證線程安全,使用了 ReentrantLock,從用法上可以看出,與 synchronized相比,ReentrantLock就稍微複雜一點。因爲必須在finally中進行解鎖操作,如果不在 finally解鎖,有可能代碼出現異常鎖沒被釋放,而synchronized是由JVM來釋放鎖。
那麼ReentrantLock到底有哪些優秀的特點呢?
1.1.1 可重入
單線程可以重複進入,但要重複退出
1
2
3
4
5
6
7
8
9
10
11
12
|
lock.lock(); lock.lock(); try { i++; }
finally { lock.unlock(); lock.unlock(); } |
由於ReentrantLock是重入鎖,所以可以反覆得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個synchronized 塊時,才釋放鎖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public
class
Child extends
Father implements
Runnable{ final
static
Child child = new
Child(); //爲了保證鎖唯一 public
static
void
main(String[] args) { for
( int
i = 0 ;
i < 50 ;
i++) { new
Thread(child).start(); } } public
synchronized
void
doSomething() { System.out.println( "1child.doSomething()" ); doAnotherThing();
//
調用自己類中其他的synchronized方法 } private
synchronized
void
doAnotherThing() { super .doSomething();
//
調用父類的synchronized方法 System.out.println( "3child.doAnotherThing()" ); } @Override public
void
run() { child.doSomething(); } } class
Father { public
synchronized
void
doSomething() { System.out.println( "2father.doSomething()" ); } } |
我們可以看到一個線程進入不同的 synchronized方法,是不會釋放之前得到的鎖的。所以輸出還是順序輸出。所以synchronized也是重入鎖
輸出:
1
2
3
4
5
6
7
8
9
10
|
1child.doSomething() 2father.doSomething() 3child.doAnotherThing() 1child.doSomething() 2father.doSomething() 3child.doAnotherThing() 1child.doSomething() 2father.doSomething() 3child.doAnotherThing() ... |
1.1.2.可中斷
與synchronized不同的是,ReentrantLock對中斷是有響應的。中斷相關知識查看高併發Java(2):多線程基礎
普通的lock.lock()是不能響應中斷的,lock.lockInterruptibly()能夠響應中斷。
我們模擬出一個死鎖現場,然後用中斷來處理死鎖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
package
test; import
java.lang.management.ManagementFactory; import
java.lang.management.ThreadInfo; import
java.lang.management.ThreadMXBean; import
java.util.concurrent.locks.ReentrantLock; public
class
Test implements
Runnable { public
static
ReentrantLock lock1 = new
ReentrantLock(); public
static
ReentrantLock lock2 = new
ReentrantLock(); int
lock; public
Test( int
lock) { this .lock
= lock; } @Override public
void
run() { try { if
(lock == 1 ) { lock1.lockInterruptibly(); try { Thread.sleep( 500 ); } catch
(Exception e) { //
TODO: handle exception } lock2.lockInterruptibly(); } else { lock2.lockInterruptibly(); try { Thread.sleep( 500 ); } catch
(Exception e) { //
TODO: handle exception } lock1.lockInterruptibly(); } } catch
(Exception e) { //
TODO: handle exception } finally { if
(lock1.isHeldByCurrentThread()) { lock1.unlock(); } if
(lock2.isHeldByCurrentThread()) { lock2.unlock(); } System.out.println(Thread.currentThread().getId()
+ ":線程退出" ); } } public
static
void
main(String[] args) throws
InterruptedException { Test
t1 = new
Test( 1 ); Test
t2 = new
Test( 2 ); Thread
thread1 = new
Thread(t1); Thread
thread2 = new
Thread(t2); thread1.start(); thread2.start(); Thread.sleep( 1000 ); //DeadlockChecker.check(); } static
class
DeadlockChecker { private
final
static
ThreadMXBean mbean = ManagementFactory .getThreadMXBean(); final
static
Runnable deadlockChecker = new
Runnable() { @Override public
void
run() { //
TODO Auto-generated method stub while
( true ) { long []
deadlockedThreadIds = mbean.findDeadlockedThreads(); if
(deadlockedThreadIds != null ) { ThreadInfo[]
threadInfos = mbean.getThreadInfo(deadlockedThreadIds); for
(Thread t : Thread.getAllStackTraces().keySet()) { for
( int
i = 0 ;
i < threadInfos.length; i++) { if (t.getId()
== threadInfos[i].getThreadId()) { t.interrupt(); } } } } try { Thread.sleep( 5000 ); } catch
(Exception e) { //
TODO: handle exception } } } }; public
static
void
check() { Thread
t = new
Thread(deadlockChecker); t.setDaemon( true ); t.start(); } } } |
上述代碼有可能會發生死鎖,線程1得到lock1,線程2得到lock2,然後彼此又想獲得對方的鎖。
我們用jstack查看運行上述代碼後的情況
的確發現了一個死鎖。
DeadlockChecker.check();方法用來檢測死鎖,然後把死鎖的線程中斷。中斷後,線程正常退出。
1.1.3.可限時
超時不能獲得鎖,就返回false,不會永久等待構成死鎖
使用lock.tryLock(long timeout, TimeUnit unit)來實現可限時鎖,參數爲時間和單位。
舉個例子來說明下可限時:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package
test; import
java.util.concurrent.TimeUnit; import
java.util.concurrent.locks.ReentrantLock; public
class
Test implements
Runnable { public
static
ReentrantLock lock = new
ReentrantLock(); @Override public
void
run() { try { if
(lock.tryLock( 5 ,
TimeUnit.SECONDS)) { Thread.sleep( 6000 ); } else { System.out.println( "get
lock failed" ); } } catch
(Exception e) { } finally { if
(lock.isHeldByCurrentThread()) { lock.unlock(); } } } public
static
void
main(String[] args) { Test
t = new
Test(); Thread
t1 = new
Thread(t); Thread
t2 = new
Thread(t); t1.start(); t2.start(); } } |
使用兩個線程來爭奪一把鎖,當某個線程獲得鎖後,sleep6秒,每個線程都只嘗試5秒去獲得鎖。
所以必定有一個線程無法獲得鎖。無法獲得後就直接退出了。
輸出:
1
|
get
lock failed |
1.1.4.公平鎖
使用方式:
1
2
3
|
public
ReentrantLock( boolean
fair) public
static
ReentrantLock fairLock = new
ReentrantLock( true ); |
一般意義上的鎖是不公平的,不一定先來的線程能先得到鎖,後來的線程就後得到鎖。不公平的鎖可能會產生飢餓現象。
公平鎖的意思就是,這個鎖能保證線程是先來的先得到鎖。雖然公平鎖不會產生飢餓現象,但是公平鎖的性能會比非公平鎖差很多。
1.2 Condition
Condition與ReentrantLock的關係就類似於synchronized與Object.wait()/signal()
await()方法會使當前線程等待,同時釋放當前鎖,當其他線程中使用signal()時或者signalAll()方法時,線 程會重新獲得鎖並繼續執行。或者當線程被中斷時,也能跳出等待。這和Object.wait()方法很相似。
awaitUninterruptibly()方法與await()方法基本相同,但是它並不會再等待過程中響應中斷。 singal()方法用於喚醒一個在等待中的線程。相對的singalAll()方法會喚醒所有在等待中的線程。這和Obejct.notify()方法很類似。
這裏就不再詳細介紹了。舉個例子來說明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
package
test; import
java.util.concurrent.locks.Condition; import
java.util.concurrent.locks.ReentrantLock; public
class
Test implements
Runnable { public
static
ReentrantLock lock = new
ReentrantLock(); public
static
Condition condition = lock.newCondition(); @Override public
void
run() { try { lock.lock(); condition.await(); System.out.println( "Thread
is going on" ); } catch
(Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public
static
void
main(String[] args) throws
InterruptedException { Test
t = new
Test(); Thread
thread = new
Thread(t); thread.start(); Thread.sleep( 2000 ); lock.lock(); condition.signal(); lock.unlock(); } } |
上述例子很簡單,讓一個線程await住,讓主線程去喚醒它。condition.await()/signal只能在得到鎖以後使用。
1.3.Semaphore
對於鎖來說,它是互斥的排他的。意思就是,只要我獲得了鎖,沒人能再獲得了。
而對於Semaphore來說,它允許多個線程同時進入臨界區。可以認爲它是一個共享鎖,但是共享的額度是有限制的,額度用完了,其他沒有拿到額度的線程還是要阻塞在臨界區外。當額度爲1時,就相等於lock
下面舉個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package
test; import
java.util.concurrent.ExecutorService; import
java.util.concurrent.Executors; import
java.util.concurrent.Semaphore; public
class
Test implements
Runnable { final
Semaphore semaphore = new
Semaphore( 5 ); @Override public
void
run() { try { semaphore.acquire(); Thread.sleep( 2000 ); System.out.println(Thread.currentThread().getId()
+ "
done" ); } catch
(Exception e) { e.printStackTrace(); } finally
{ semaphore.release(); } } public
static
void
main(String[] args) throws
InterruptedException { ExecutorService
executorService = Executors.newFixedThreadPool( 20 ); final
Test t = new
Test(); for
( int
i = 0 ;
i < 20 ;
i++) { executorService.submit(t); } } } |
有一個20個線程的線程池,每個線程都去 Semaphore的許可,Semaphore的許可只有5個,運行後可以看到,5個一批,一批一批地輸出。
當然一個線程也可以一次申請多個許可
1
|
public
void
acquire( int
permits) throws
InterruptedException |
1.4 ReadWriteLock
ReadWriteLock是區分功能的鎖。讀和寫是兩種不同的功能,讀-讀不互斥,讀-寫互斥,寫-寫互斥。
這樣的設計是併發量提高了,又保證了數據安全。
使用方式:
1
2
3
|
private
static
ReentrantReadWriteLock readWriteLock= new
ReentrantReadWriteLock(); private
static
Lock readLock = readWriteLock.readLock(); private
static
Lock writeLock = readWriteLock.writeLock(); |
詳細例子可以查看 Java實現生產者消費者問題與讀者寫者問題,這裏就不展開了。
1.5 CountDownLatch
倒數計時器
一種典型的場景就是火箭發射。在火箭發射前,爲了保證萬無一失,往往還要進行各項設備、儀器的檢查。 只有等所有檢查完畢後,引擎才能點火。這種場景就非常適合使用CountDownLatch。它可以使得點火線程
,等待所有檢查線程全部完工後,再執行
使用方式:
1
2
3
|
static
final
CountDownLatch end = new
CountDownLatch( 10 ); end.countDown();
end.await(); |
示意圖:
一個簡單的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
package
test; import
java.util.concurrent.CountDownLatch; import
java.util.concurrent.ExecutorService; import
java.util.concurrent.Executors; public
class
Test implements
Runnable { static
final
CountDownLatch countDownLatch = new
CountDownLatch( 10 ); static
final
Test t = new
Test(); @Override public
void
run() { try { Thread.sleep( 2000 ); System.out.println( "complete" ); countDownLatch.countDown(); } catch
(Exception e) { e.printStackTrace(); } } public
static
void
main(String[] args) throws
InterruptedException { ExecutorService
executorService = Executors.newFixedThreadPool( 10 ); for
( int
i = 0 ;
i < 10 ;
i++) { executorService.execute(t); } countDownLatch.await(); System.out.println( "end" ); executorService.shutdown(); } } |
主線程必須等待10個線程全部執行完纔會輸出”end”。
1.6 CyclicBarrier
和CountDownLatch相似,也是等待某些線程都做完以後再執行。與CountDownLatch區別在於這個計數器可以反覆使用。比如,假設我們將計數器設置爲10。那麼湊齊第一批1 0個線程後,計數器就會歸零,然後接着湊齊下一批10個線程
使用方式:
1
2
3
4
5
|
public
CyclicBarrier( int
parties, Runnable barrierAction) barrierAction就是當計數器一次計數完成後,系統會執行的動作 await() |
示意圖:
下面舉個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
package
test; import
java.util.concurrent.CyclicBarrier; public
class
Test implements
Runnable { private
String soldier; private
final
CyclicBarrier cyclic; public
Test(String soldier, CyclicBarrier cyclic) { this .soldier
= soldier; this .cyclic
= cyclic; } @Override public
void
run() { try { //等待所有士兵到齊 cyclic.await(); dowork(); //等待所有士兵完成工作 cyclic.await(); } catch
(Exception e) { //
TODO Auto-generated catch block e.printStackTrace(); } } private
void
dowork() { //
TODO Auto-generated method stub try { Thread.sleep( 3000 ); } catch
(Exception e) { //
TODO: handle exception } System.out.println(soldier
+ ":
done" ); } public
static
class
BarrierRun implements
Runnable { boolean
flag; int
n; public
BarrierRun( boolean
flag, int
n) { super (); this .flag
= flag; this .n
= n; } @Override public
void
run() { if
(flag) { System.out.println(n
+ "個任務完成" ); } else { System.out.println(n
+ "個集合完成" ); flag
= true ; } } } public
static
void
main(String[] args) { final
int
n = 10 ; Thread[]
threads = new
Thread[n]; boolean
flag = false ; CyclicBarrier
barrier = new
CyclicBarrier(n, new
BarrierRun(flag, n)); System.out.println( "集合" ); for
( int
i = 0 ;
i < n; i++) { System.out.println(i
+ "報道" ); threads[i]
= new
Thread( new
Test( "士兵"
+ i, barrier)); threads[i].start(); } } } |
打印結果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
集合 0 報道 1 報道 2 報道 3 報道 4 報道 5 報道 6 報道 7 報道 8 報道 9 報道 10 個集合完成 士兵 5 :
done 士兵 7 :
done 士兵 8 :
done 士兵 3 :
done 士兵 4 :
done 士兵 1 :
done 士兵 6 :
done 士兵 2 :
done 士兵 0 :
done 士兵 9 :
done 10 個任務完成 |
1.7 LockSupport
提供線程阻塞原語
和suspend類似
1
2
|
LockSupport.park();
LockSupport.unpark(t1); |
與suspend相比 不容易引起線程凍結
LockSupport的思想呢,和 Semaphore有點相似,內部有一個許可,park的時候拿掉這個許可,unpark的時候申請這個許可。所以如果unpark在park之前,是不會發生線程凍結的。
下面的代碼是高併發Java(2):多線程基礎中suspend示例代碼,在使用suspend時會發生死鎖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
package
test; import
java.util.concurrent.locks.LockSupport; public
class
Test { static
Object u = new
Object(); static
TestSuspendThread t1 = new
TestSuspendThread( "t1" ); static
TestSuspendThread t2 = new
TestSuspendThread( "t2" ); public
static
class
TestSuspendThread extends
Thread { public
TestSuspendThread(String name) { setName(name); } @Override public
void
run() { synchronized
(u) { System.out.println( "in
"
+ getName()); //Thread.currentThread().suspend(); LockSupport.park(); } } } public
static
void
main(String[] args) throws
InterruptedException { t1.start(); Thread.sleep( 100 ); t2.start(); //
t1.resume(); //
t2.resume(); LockSupport.unpark(t1); LockSupport.unpark(t2); t1.join(); t2.join(); } } |
而使用 LockSupport則不會發生死鎖。
另外
park()能夠響應中斷,但不拋出異常。中斷響應的結果是,park()函數的返回,可以從Thread.interrupted()得到中斷標誌。
在JDK當中有大量地方使用到了park,當然LockSupport的實現也是使用unsafe.park()來實現的。
1
2
3
|
public
static
void
park() { unsafe.park( false ,
0L); } |
1.8 ReentrantLock 的實現
下面來介紹下ReentrantLock的實現,ReentrantLock的實現主要由3部分組成:
- CAS狀態
- 等待隊列
- park()
ReentrantLock的父類中會有一個state變量來表示同步的狀態
1
2
3
4
|
/** *
The synchronization state. */ private
volatile
int
state; |
通過CAS操作來設置state來獲取鎖,如果設置成了1,則將鎖的持有者給當前線程
1
2
3
4
5
6
|
final
void
lock() { if
(compareAndSetState( 0 ,
1 )) setExclusiveOwnerThread(Thread.currentThread()); else acquire( 1 ); } |
如果拿鎖不成功,則會做一個申請
1
2
3
4
5
|
public
final
void
acquire( int
arg) { if
(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE),
arg)) selfInterrupt(); } |
首先,再去申請下試試看tryAcquire,因爲此時可能另一個線程已經釋放了鎖。
如果還是沒有申請到鎖,就addWaiter,意思是把自己加到等待隊列中去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
Node addWaiter(Node mode) { Node
node = new
Node(Thread.currentThread(), mode); //
Try the fast path of enq; backup to full enq on failure Node
pred = tail; if
(pred != null )
{ node.prev
= pred; if
(compareAndSetTail(pred, node)) { pred.next
= node; return
node; } } enq(node); return
node; } |
其間還會有多次嘗試去申請鎖,如果還是申請不到,就會被掛起
1
2
3
4
|
private
final
boolean
parkAndCheckInterrupt() { LockSupport.park( this ); return
Thread.interrupted(); } |
同理,如果在unlock操作中,就是釋放了鎖,然後unpark,這裏就不具體講了。
2. 併發容器及典型源碼分析
2.1 ConcurrentHashMap
我們知道HashMap不是一個線程安全的容器,最簡單的方式使HashMap變成線程安全就是使用Collections.synchronizedMap,它是對HashMap的一個包裝
1
|
public
static
Map m=Collections.synchronizedMap( new
HashMap()); |
同理對於List,Set也提供了相似方法。
但是這種方式只適合於併發量比較小的情況。
我們來看下synchronizedMap的實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
private
final
Map<K,V> m; //
Backing Map final
Object mutex; //
Object on which to synchronize SynchronizedMap(Map<K,V>
m) { if
(m== null ) throw
new
NullPointerException(); this .m
= m; mutex
= this ; } SynchronizedMap(Map<K,V>
m, Object mutex) { this .m
= m; this .mutex
= mutex; } public
int
size() { synchronized
(mutex) { return
m.size();} } public
boolean
isEmpty() { synchronized
(mutex) { return
m.isEmpty();} } public
boolean
containsKey(Object key) { synchronized
(mutex) { return
m.containsKey(key);} } public
boolean
containsValue(Object value) { synchronized
(mutex) { return
m.containsValue(value);} } public
V get(Object key) { synchronized
(mutex) { return
m.get(key);} } public
V put(K key, V value) { synchronized
(mutex) { return
m.put(key, value);} } public
V remove(Object key) { synchronized
(mutex) { return
m.remove(key);} } public
void
putAll(Map<? extends
K, ? extends
V> map) { synchronized
(mutex) {m.putAll(map);} } public
void
clear() { synchronized
(mutex) {m.clear();} } |
它會將HashMap包裝在裏面,然後將HashMap的每個操作都加上synchronized。
由於每個方法都是獲取同一把鎖(mutex),這就意味着,put和remove等操作是互斥的,大大減少了併發量。
下面來看下ConcurrentHashMap是如何實現的
1
2
3
4
5
6
7
8
9
10
11
|
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 ); } |
在 ConcurrentHashMap內部有一個Segment段,它將大的HashMap切分成若干個段(小的HashMap),然後讓數據在每一段上Hash,這樣多個線程在不同段上的Hash操作一定是線程安全的,所以只需要同步同一個段上的線程就可以了,這樣實現了鎖的分離,大大增加了併發量。
在使用ConcurrentHashMap.size時會比較麻煩,因爲它要統計每個段的數據和,在這個時候,要把每一個段都加上鎖,然後再做數據統計。這個就是把鎖分離後的小小弊端,但是size方法應該是不會被高頻率調用的方法。
在實現上,不使用synchronized和lock.lock而是儘量使用trylock,同時在HashMap的實現上,也做了一點優化。這裏就不提了。
2.2 BlockingQueue
BlockingQueue不是一個高性能的容器。但是它是一個非常好的共享數據的容器。是典型的生產者和消費者的實現。
示意圖:
具體可以查看Java實現生產者消費者問題與讀者寫者問題