文章目錄
更多關於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();
}
}
}
});
}
執行結果: