在上篇文章本人粗略地整理了AbstractQueuedSynchronizer和ReentrantLock的源碼要點。其實,在java.util.concurrent包中,AbstractQueuedSynchronizer的應用很是普遍,而不侷限於在ReentrantLock中的實現,本文簡要介紹下AbstractQueuedSynchronizer在Semaphore、ReentrantReadWriteLock等類中的應用。 java
0. 回顧 安全
上文在介紹AQS的時候,介紹了AQS和ReentrantLock類中的Sync子類互相配合完成可重入鎖的實現,在這其中AQS所提供的是一套靈活和完整的隊列處理機制。因爲在AQS中已經提供了完整的隊列處理機制,一般是不須要擴展的子類Override的。同時,AQS又提供了state屬性和tryAcquire()/tryRelease()等方法,而這些正是須要子類根據具體的需求邏輯靈活實現的擴展點。從ReentrantLock、ReentrantReadWriteLock、Semaphore和CountDownLatch的實現來看,一般是在這些工具類的中封裝實現本身獨有的Sync內部類,而Sync就是對AQS的擴展實現。 多線程
1. Semaphore 併發
學習操做系統理論課的時候,教材上應該都會講過信號量這種概念,java.util.concurrent.Semaphore類就是Java中這個概念的實現。好比資源R有5個實體,若是每一個線程執行的過程當中須要用到1個,那麼容許5個線程併發執行,第6個會等待其餘線程釋放資源後繼續執行。 dom
先看一個應用Semaphore的例子:
其中最主要的方法就是acquire()和release(),更詳細的能夠參看Oracle的API文檔。 ide
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
ExecutorService exec = Executors.newCachedThreadPool();
finalSemaphore sem =newSemaphore(5);
for(inti =1; i <100; i++) {
finalinttid = i;
Runnable semTask =newRunnable() {
publicvoidrun() {
try{
sem.acquire();
System.out.println("running thread with id: "+ tid);
Thread.sleep((long) (Math.random() *3000));
System.out.println("completing with id: "+ tid);
sem.release();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(semTask);
}
exec.shutdown();
|
下面看看Semaphore的實現。若是熟悉ReentrantLock的實現,那麼Semaphore其實很好理解,簡單來說,Semaphore實際上就是把鎖的限制從1變爲N。 高併發
2. CountDownLatch 工具
這個類主要是爲了解決多線程中的狀態依賴問題。java.util.concurrent.CountDownLatch這個類從名字就能夠看出,是以一個遞減計數器爲基礎,多個線程共享這樣一個對象,開啓一個計數值,某些線程能夠等待這個計數器值爲0的時候繼續任務,調用await(),而那些改變狀態的線程須要作的就是使計數器遞減,調用countDown()方法。 學習
看一個例子。 ui
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
|
ExecutorService exec = Executors.newCachedThreadPool();
finalCountDownLatch cdl =newCountDownLatch(3);
Runnable watingTasks =newRunnable() {
publicvoidrun() {
try{
System.out.println("there're 3 tasks here. if all tasks are finished, i will go home.");
System.out.println("working...");
cdl.await();
System.out.println("ok, i will go home now!");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(watingTasks);
for(inti =0; i <3; i++) {
finalinttid = i;
Runnable semTask =newRunnable() {
publicvoidrun() {
try{
System.out.println("starting task "+ tid + "...");
Thread.sleep((long) (Math.random() *5000));
System.out.println("task "+ tid + " finished");
cdl.countDown();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(semTask);
}
exec.shutdown();
|
和Semaphore相似,在使用AQS的實現上,主要有如下幾點。
3. ReentrantReadWriteLock
在高併發場景下,爲了讓任務執行更有效率,將讀和寫場景分離是有必要的。這是由於讀和寫在線程安全方面特色的不一樣,讀不改變狀態,多個線程是能夠同時進行而沒有問題的,而寫與寫、寫與讀之間都是須要互斥的。在java.util.concurrent中,ReentrantReadWriteLock這個類就是作這個事情的。
具體的使用就不舉例了,直接分析下其實現。從類名上看ReentrantLock和ReentrantReadWriteLock就很類似,實現上也有類似的部分。ReentrantReadWriteLock中封裝了ReadLock和WriteLock內部類,而ReentrantReadWriteLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock中都有本身的Sync類屬性,使用的是ReentrantReadWriteLock.Sync實現,並且對象關係上,ReadLock和WriteLock中的sync都是指向ReentrantReadWriteLock對象中的sync引用,即便用了同一個AQS同一套隊列,只是將方法分離開來處理。
其中的要點:
4. 最後再說下CyclicBarrier類
在這裏說java.util.concurrent.CyclicBarrier,並不是由於其使用了AQS,而是由於它的用法和CountDownLatch有相似之處。CyclicBarrier和CountDownLatch都是處理狀態依賴的問題的,而不一樣之處是使用CyclicBarrier的線程互相依賴,即互相等待,直到達到某一特定狀態,這些線程同時繼續執行。
CyclicBarrier是基於ReetrantLock和ConditionObject的,await()的時候對計數器遞減,並檢查是否爲0,若是爲0則執行CyclicBarrier類對象的barrierCommand(Runnable類對象屬性)並signalAll()通知全部等待線程開始下一輪,不然阻塞當前線程。
本文對java.util.concurrent的併發工具類和AQS的應用作了簡要的整理,更多併發的內容會在後面的文章整理。