版權聲明:本文爲博主原創文章,未經博主允許不得轉載。
Java.util.concurrent 包在java語言中可以說是比較難啃的一塊,但理解好這個包下的知識,對學習java來說,不可謂是一種大的提升,我也嘗試着用自己不聰明的腦袋努力的慢慢啃下點東西來。其實 java.util.concurrent 包中,最核心的就是AQS( AbstractQueuedSynchronizer) 這個抽象類,可以說是整個JUC包的基石,但今天先不說AQS,我先從比較容易理解的 Condition 條件講起。
什麼是Condition條件
Condition 是定義在 java.util.concurrent.locks 包下的一個接口,這個接口主要的功能就是實現了與Object類中的wait(),notify()方法相同的語義,但是功能上更強大。Condition 接口中定義的方式其實很少,列舉下來:
//使當前線程接到signal信號之前,或者被中斷之前一直處於等待狀態
void await() throws InterruptedException;
//使當前線程接到signal信號之前一直處於等待狀態
void awaitUninterruptibly();
//使當前線程接到signal信號之前,或到達指定等待的時間之前,或者被中斷之前一直處於等待狀態
boolean await(long time, TimeUnit unit) throws InterruptedException;
//使當前線程接到signal信號之前,或到達指定的最後期限時間之前,或者被中斷之前一直處於等待狀態
boolean awaitUntil(Date deadline) throws InterruptedException;
//使當前線程接到signal信號之前,或到達指定等待的時間之前,或者被中斷之前一直處於等待狀態
long awaitNanos(long nanosTimeout) throws InterruptedException;
//向一個線程發送喚醒信號
void signal();
//向所有線程發送喚醒信號
void signalAll();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
從定義的方法中也可以看出這個類的功能,無非就兩種:等待方法、喚醒等待方法。
Condition 的使用
下面用一個之前用的小demo展示一下 Condition 的使用方法:
先使用Object中的wait,notify 方法:
public class TestCondition {
public static void main(String[] args) {
try {
ThreadTest t1 = new ThreadTest("t1");
synchronized (t1) {
System.out.println(Thread.currentThread().getName()+"線程啓動線程 t1");
t1.start();
System.out.println(Thread.currentThread().getName()+"線程執行wait方法,等待被喚醒");
t1.wait();
System.out.println(Thread.currentThread().getName()+"線程繼續執行");
}
} catch (InterruptedException e)
e.printStackTrace();
}
}
}
class ThreadTest extends Thread{
public ThreadTest(String name){
super(name);
}
public void run(){
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"線程執行 notify 方法");
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
- 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
運行結果:
main線程啓動線程 t1
main線程執行wait方法,等待被喚醒
t1線程執行 notify 方法
main線程繼續執行
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
這裏就不再詳述關於 synchronized 關鍵字的使用,舉這個示例是爲了引出Condition的使用。
稍微改一下上面的程序:
public class TestCondition {
private static ReentrantLock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
try {
ThreadTest t1 = new ThreadTest("t1");
lock.lock();
System.out.println(Thread.currentThread().getName()+"線程啓動線程 t1");
t1.start();
System.out.println(Thread.currentThread().getName()+"線程執行condition.await()方法,等待被喚醒");
condition.await();
System.out.println(Thread.currentThread().getName()+"線程繼續執行");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
static class ThreadTest extends Thread{
public ThreadTest(String name){
super(name);
}
public void run(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName()+"線程執行condition.signal()方法");
condition.signal();
}finally{
lock.unlock();
}
}
}
}
- 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
- 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
運行結果:
main線程啓動線程 t1
main線程執行condition.await()方法,等待被喚醒
t1線程執行condition.signal()方法
main線程繼續執行
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
根據上面的實例可以看出,Condition工具類是配合Lock類一起使用的,當然Condition的作用遠不止上面代碼這樣簡單,其實它最主要的作用是可以在同一把鎖上,針對不同的業務使用不用的Condition。比如在前面的文章提到的生產消費問題,我們完全可以使用兩個Condition,一個針對於生產,一個針對於消費,當產品爲0時,我們可以讓消費的Condition執行await方法,當產品不爲0時,可以讓消費的Condition執行signal方法。生產者也是類似,具體代碼這裏就不再詳述了,可以嘗試自己實現一下。
Condition 的實現類:ConditionObject
Condition 是一個接口,那它到底是怎麼工作的呢?我們來看一下ReentrantLock 類是怎麼樣使用Condition 的。
Condition condition = lock.newCondition();//這是生成Condition 的方法
- 1
- 1
追蹤一下newCondition( )這個方法,我們就可以看到一個Condition 的具體實現:
public Condition newCondition() {
return sync.newCondition(); //調用sync的newCondition
}
//sync的方法,返回一個new ConditionObject()
final ConditionObject newCondition() {
return new ConditionObject();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這個sync是什麼呢?它是定義在ReentrantLock 中的內部類,它繼承了上面提到的AQS這個抽象類,從某種角度來說ReentrantLock 只是提供了一個可以操作AQS這個核心類的入口,代理了一些重要的方法,那爲什麼不讓ReentrantLock 直接繼承AQS,而是選擇用一個內部類來實現呢,可能是出於一些安全性方面的考慮。ConditionObject是定義在AQS中的。
Condition 與 AQS 到底是怎麼工作的?
探究這個問題,就需要來看一下ConditionObject的源碼了。
代表性的,我們看一下await方法和signal方法的源碼(基於jdk1.8.0_112):
await方法:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException(); //判斷線程的中斷狀態,如果中斷則拋出異常
//將當前線程包裝成一個條件等待節點添加到ConditionObject維護的一個隊列中
Node node = addConditionWaiter();
//釋放當前線程佔有的鎖
int savedState = fullyRelease(node);
int interruptMode = 0;
//這個while 循環就是在當前線程釋放鎖後,一直觀察持有自己線程的節點有沒有被加載到
//AQS維護的等待隊列中(加入到這個隊列中才有獲取鎖的資格),什麼時候會加入到這個隊列中呢?
//當然是執行了喚醒這個線程對應的singal方法的時候啦
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
在看一下signal方法:
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;//ConditionObject維護隊列中的頭節點,進行喚醒操作
if (first != null)
doSignal(first);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
從上面的敘述不難看出,AQS 和 ConditionObject各自維護了一個隊列,用來存放包裝了線程的Node節點。聯繫上面的代碼示例和源碼可以總結一下AQS和ConditionObject中的隊列是怎麼被維護的:
(1) 示例中存在兩個線程:main線程 t1線程
(2) 當main線程調用lock.locak()方法時,main獲取到鎖,繼續執行, 當執行到t1.start()方法時,t1也想獲取lock,但是此時該鎖已經被main線程佔用,所以t1線程進入 AQS維護的等待隊列中,等待機會獲取cpu的佔用權。
(3) 當main線程執行了condition.await()方法時,main線程就在釋放佔用鎖的同時加入到了ConditionObject維護的等待隊列中,在這個隊列中的線程,如果signal狀態不發生改變,是永遠沒有機會獲取到cpu的佔有權的。
(4) 好,這個時候main已經進入了ConditionObject維護的等待隊列中,那麼AQS維護的等待隊列中的t1線程就可以獲取cpu的佔有權了,可以繼續執行。
(5) 當t1線程執行到 condition.signal() 方法時,就會喚醒ConditionObject維護的等待隊列中的頭節點,也就是main線程。但是注意,這裏喚醒的意思是將main線程節點放到AQS維護的等待隊列中,然後聽從AQS的調度,並不是馬上就能獲取cpu的佔有權。
(6) 然後t1線程執行結束,unlock釋放佔用的鎖,在AQS維護的等待隊列中的main就能繼續執行下去了。
總之:
- ConditionObject維護的等待隊列的功能是存放那些執行了await方法的線程,等待收到signal信息好可以進入AQS維護的等待隊列中。在這個隊列中是不會獲取到鎖的。
- AQS維護的等待隊列存放那些就緒狀態的線程,只等待目前佔有鎖的傢伙執行完或者進入了ConditionObject維護的等待隊列中之後,來進行競爭獲取鎖。只有在這個隊列中才有資格(並不一定會)獲取鎖。