Condition 将 Object 螢幕方法(wait、notify和 notifyAll)分解成截然不同的對象,以便通過将這些對象與任意 Lock 實作組合使用,為每個對象提供多個等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 螢幕方法的使用。
條件(也稱為條件隊列或條件變量)為線程提供了一個含義,以便在某個狀态條件現在可能為 true 的另一個線程通知它之前,一直挂起該線程(即讓其“等待”)。因為通路此共享狀态資訊發生在不同的線程中,是以它必須受保護,是以要将某種形式的鎖定與該條件相關聯。等待提供一個條件的主要屬性是:以原子方式釋放相關的鎖定,并挂起目前線程,就像 Object.wait 做的那樣。
Condition 執行個體實質上被綁定到一個鎖定上。要為特定 Lock 執行個體獲得 Condition 執行個體,請使用其 newCondition() 方法。
作為一個示例,假定有一個綁定的緩沖區,它支援 put 和 take 方法。如果試圖在空的緩沖區上執行 take 操作,則在某一個項變得可用之前,線程将一直阻塞;如果試圖在滿的緩沖區上執行 put 操作,則在有空間變得可用之前,線程将一直阻塞。我們喜歡在單獨的等待 set 中儲存 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;
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;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
(ArrayBlockingQueue類提供了這項功能,是以沒有理由去實作這個示例類。)
Condition 實作可以提供不同于 Object 螢幕方法的行為和語義,比如受保證的通知排序,或者在執行通知時不需要保持一個鎖定。如果某個實作提供了這樣特殊的語義,則該實作必須記錄這些語義。
注意,Condition 執行個體隻是一些普通的對象,它們自身可以用作 synchronized 語句中的目标,并且可以調用自己的 wait 和 notification 螢幕方法。擷取 Condition 執行個體的螢幕鎖定或者使用其螢幕方法,與擷取和該 Condition 相關的 Lock 或使用其 waiting 和 signalling 方法沒有什麼特定的關系。為了避免混淆,建議除了在其自身的實作中之外,切勿以這種方式使用 Condition 執行個體。
執行個體:
package com.bijian.thread;
public class SaveThread extends Thread {
private String name; // 操作人
private MyCount myCount; // 賬戶
private int x; // 存款金額
public SaveThread(String name, MyCount myCount, int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
public void run() {
myCount.saving(x, name);
}
}
package com.bijian.thread;
public class DrawThread extends Thread {
private String name; // 操作人
private MyCount myCount; // 賬戶
private int x; // 存款金額
DrawThread(String name, MyCount myCount, int x) {
this.name = name;
this.myCount = myCount;
this.x = x;
}
public void run() {
myCount.drawing(x, name);
}
}
package com.bijian.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyCount {
private String oid; // 賬号
private int cash; // 賬戶餘額
private Lock lock = new ReentrantLock(); // 賬戶鎖
private Condition _save = lock.newCondition(); // 存款條件
private Condition _draw = lock.newCondition(); // 取款條件
MyCount(String oid, int cash) {
this.oid = oid;
this.cash = cash;
}
public void saving(int x, String name) {
lock.lock(); // 擷取鎖
try {
if (x > 0) {
cash += x; // 存款
System.out.println(name + "存款" + x + ",目前餘額為" + cash);
}
_draw.signalAll(); // 喚醒所有等待線程。
} finally {
lock.unlock();
}
}
public void drawing(int x, String name) {
lock.lock(); // 擷取鎖
try {
if (cash - x < 0) {
_draw.await(); // 阻塞取款操作
}
cash -= x; // 取款
System.out.println(name + "取款" + x + ",目前餘額為" + cash);
_save.signalAll(); // 喚醒所有存款操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 釋放鎖
}
}
}
package com.bijian.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
//建立并發通路的賬戶
MyCount myCount = new MyCount("95599200901215522", 10000);
//建立一個線程池
ExecutorService pool = Executors.newFixedThreadPool(2);
Thread t1 = new SaveThread("張三", myCount, 2000);
Thread t2 = new SaveThread("李四", myCount, 3600);
Thread t3 = new DrawThread("王五", myCount, 2700);
Thread t4 = new SaveThread("老張", myCount, 600);
Thread t5 = new DrawThread("老牛", myCount, 1300);
Thread t6 = new DrawThread("胖子", myCount, 800);
//執行各個線程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t6);
pool.execute(t5);
pool.execute(t4);
//關閉線程池
pool.shutdown();
}
}
運作結果:
張三存款2000,目前餘額為12000
李四存款3600,目前餘額為15600
王五取款2700,目前餘額為12900
胖子取款800,目前餘額為12100
老張存款600,目前餘額為12700
老牛取款1300,目前餘額為11400