Java併發程式設計(11)-條件變數Condition的使用
文章目錄
更多關於Java併發程式設計的文章請點選這裡:Java併發程式設計實踐(0)-目錄頁
Condition在Java併發程式設計中是一個十分常用的介面,被稱為條件變數,常與顯式鎖和可重入鎖一起使用,它可以在某些條件下使執行緒進行休眠或者喚醒,本文將以實現生產者-消費者模式的佇列作為demo讓大家對條件變數 有初步的瞭解。
一、併發程式設計中的條件變數
1.1、從生產者-消費者模型理解條件變數
生產者-消費者模型是一種常見的模型,在《Java併發程式設計實踐》中有一個例子很好地解釋了這種模式:
兩個洗盤子的勞動分工也是一個與生產者-消費者設計相類似的例子:一個人洗盤子,並把洗好的盤子放到盤子架上,另一個人從盤子架上得到盤子,並把它烘乾。在這個場景中,盤子架充當了阻塞佇列;如果架子上沒有盤子,消費者會一直等待,直到有盤子需要烘乾,如果盤子架被放滿了,那麼生產者會停止洗盤子,直到架上有新的空間可用。
在這個例子中哪裡看出了條件變數呢?
這個條件變數就是盤子架是否滿了,當盤子架滿了,那麼生產者等待盤子架有空餘的空間時才開始工作,當盤子架空了,消費者等待其有碗了才開始工作,這就可以很好地理解條件變量了:條件變數就是在某些條件下使執行緒進行休眠或者喚醒的一種工作機制。
1.2、Condition介面
Condition介面
是Java併發程式設計中的環境變數,它位於java.util.concurrent.locks包下,常與顯式鎖一起使用,使用Lock.newCondition()獲得Condition物件。
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
void signal();
void signalAll();
}
1.3、Condition介面方法
以上是Condition介面定義的方法,await
對應於Object.wait
,signal
對應於Object.notify
,signalAll
對應於Object.notifyAll
。特別說明的是Condition的介面改變名稱就是為了避免與Object中的wait/notify/notifyAll的語義和使用上混淆,因為Condition同樣有wait/notify/notifyAll方法。
- await()方法:
造成當前執行緒在接到訊號或被中斷之前一直處於等待狀態。 - signal()方法:
喚醒特定的等待執行緒。 - signalAll()方法:
喚醒所有的等待執行緒。
二、實現一個生產者-消費者中的條件佇列
2.1、條件變數的一般使用模式
lock.lock();
try{
while(條件判斷){
acondition.await();
}
//dosomething...
bconditon.signal();
}finally{
lock.unlock();
}
2.2、使用條件變數實現一個生產者-消費者模式的佇列
- ConditionQuene
static class ConditionQuene{
//quene長度
private Integer size;
//可重入鎖
private Lock lock = new ReentrantLock();
//條件變數(對列滿或空)
private Condition isFull = lock.newCondition();
private Condition isEmpty = lock.newCondition();
//無界佇列
private BlockingQueue<Integer> quene = new LinkedBlockingQueue<Integer>();
public ConditionQuene(Integer size){
this.size = size;
}
//向有界佇列中新增元素
public void add(Integer value) throws InterruptedException {
lock.lock();//執行到該處的執行緒獲得鎖
try {
while(quene.size() == size){
//佇列已滿,讓執行緒在 isFull這個條件變數中等待
isFull.await();
}
quene.add(value);
System.out.println("執行緒"+Thread.currentThread().getName()+"已將元素"+value+"已經放入佇列中,當前空餘容量為:"+(size - quene.size()));
isEmpty.signal();//喚醒在 isEmpty 條件變數下等待的執行緒
}finally {
lock.unlock();//釋放鎖
}
}
//向有界佇列中刪除元素
public void delete(Integer value) throws InterruptedException {
lock.lock();//執行到該處的執行緒獲得鎖
try {
while(quene.size() == 0){
//佇列為空,讓執行緒在 isEmpty 這個條件變數中等待
isEmpty.await();
}
quene.remove(value);
System.out.println("執行緒"+Thread.currentThread().getName()+"已將元素"+value+"已經從佇列中刪除s,當前空餘容量為:"+(size - quene.size()));
isFull.signal();//喚醒在 isFull 條件變數下等待的執行緒
}finally {
lock.unlock();//釋放鎖
}
}
}
- main方法測試
public static void main(String[] args) throws InterruptedException {
//執行緒排程器->含有兩個執行緒
Executor executor = Executors.newFixedThreadPool(2);
//建立一個只能容納5個元素的條件佇列
ConditionQuene conditionQuene = new ConditionQuene(5);
executor.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
conditionQuene.add(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
conditionQuene.delete(i);
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
執行結果: