一、簡介
JUC提供了Lock可以方便的進行鎖操作,但是有時候我們也需要對線程進行條件性的阻塞和喚醒,這時我們就需要Condition條件變量,可以方便的對持有鎖的線程進行阻塞和喚醒。
Condition將對象監視器方法(wait、notify和notifyAll)分解到不同的對象中,通過將它們與任意鎖實現結合使用,實現每個對象具有多個等待集的效果。鎖代替同步方法和語句的使用,Condition代替對象監視器方法的使用。
Condition(也稱爲條件隊列或條件變量)提供了一種方法,讓一個線程暫停執行(“等待”),直到另一個線程通知某個狀態條件現在可能爲真。因爲對共享狀態信息的訪問發生在不同的線程中,所以必須保護它,所以某種形式的鎖與條件相關聯。等待條件提供的關鍵屬性是它自動釋放關聯的鎖並掛起當前線程,就像Object.wait一樣。
Condition實例本質上綁定到鎖。要獲取特定鎖實例的條件實例,請使用其newCondition()方法。
- 例如,假設我們有一個有界的緩衝區,它支持put和take方法。如果嘗試在空緩衝區上執行take操作,則線程將阻塞,直到某個項可用爲止;如果在一個完整的緩衝區上嘗試put,那麼線程將阻塞,直到空間可用爲止。我們希望將put線程和take線程放在不同的等待集中,這樣我們就可以優化在緩衝區中的項或空間可用時只通知單個線程。這可以通過使用兩個Condition條件實例來實現。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
//非滿的條件
final Condition notFull = lock.newCondition();
//非空的條件
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
//如果滿了,則阻塞等待
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
//通知take
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
//如果當前爲空, 則等待
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
//通知需要進行put
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
二、常用API
void |
await() 導致當前線程等待,直到發出信號或中斷它。 |
boolean |
await(long time, TimeUnit unit) 導致當前線程等待,直到發出信號或中斷它,或指定的等待時間過期。 |
long |
awaitNanos(long nanosTimeout) 導致當前線程等待,直到發出信號或中斷它,或指定的等待時間過期。 |
void |
導致當前線程等待,直到它被通知。 |
boolean |
awaitUntil(Date deadline) 導致當前線程等待,直到發出信號或中斷它,或指定的截止日期過期。 |
void |
signal() 喚醒一個正在等待的線程。 |
void |
喚醒所有等待的線程。 |
更加詳細的介紹,可以去官網https://docs.oracle.com/javase/8/docs/api/index.html瞭解。
三、案例
下面通過一個精確通知順序訪問的案例加深對Condition的使用。
示例:多線程之間按順序調用,實現AA->BB->CC依次打印。
案例分析:
要實現按順序打印,那麼是否要有一些標誌,標誌當前需要打印AA還是BB還是CC:
/**
* 標誌位
* A :1
* B : 2
* C : 3
*/
private int number = 1;
當打印完AA後,將number修改爲2,打印完BB後,將n的umber修改爲3,打印完CC後將number修改爲1,依次重複,然後再創建三個分別代表打印AA/BB/CC的Condition綁定到Lock上。
代碼實現:
/**
* 多線程之間按順序調用,實現AA->BB->CC依次打印
* 也即精確通知順序訪問
*/
public class T07_ConditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
shareData.printAA();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
shareData.printBB();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 3; i++) {
shareData.printCC();
System.out.println("===================");
}
}).start();
}
}
class ShareData {
/**
* 標誌位
* A :1
* B : 2
* C : 3
*/
private int number = 1;
/**
* 可重入鎖
*/
private Lock lock = new ReentrantLock();
/**
* 打印AA的條件
*/
private Condition conditionA = lock.newCondition();
/**
* 打印BB的條件
*/
private Condition conditionB = lock.newCondition();
/**
* 打印CC的條件
*/
private Condition conditionC = lock.newCondition();
public void printAA() {
lock.lock();
try {
while (number != 1) {
conditionA.await();
}
for (int i = 0; i <= 4; i++) {
System.out.println("AA");
}
number = 2;
//喚醒打印BB的線程
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printBB() {
lock.lock();
try {
while (number != 2) {
conditionB.await();
}
for (int i = 0; i < 5; i++) {
System.out.println("BB");
}
number = 3;
//喚醒打印CC的線程
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printCC() {
lock.lock();
try {
while (number != 3) {
conditionC.await();
}
for (int i = 0; i < 5; i++) {
System.out.println("CC");
}
number = 1;
//喚醒打印AA的線程
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
運行結果:
AA
AA
AA
AA
AA
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
AA
AA
AA
AA
AA
===================
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
===================
AA
AA
AA
AA
AA
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
===================
以上就是使用Condition實現了精確通知某個線程然後按順序訪問。