使用notify()而不是notifyAll()是一種優化。使用notify()時,在衆多等待同一個鎖的任務中只有一個會被喚醒,因此如果你希望使用notify(),就必須保證被喚醒的是恰當的任務。另外,爲了使用notify(),所有任務必須等待相同的條件,因爲如果你有多個任務在等待不同的條件,那麼你就不會知道是否喚醒了恰當的任務。如果使用notify(),當條件發生變化時,必須只有一個任務能夠從中受益。最後,這些限制對所有可能存在的子類都必須總是起作用。如果這些規則中有任何一條不滿足,那麼你就必須使用notifyAll()而不是notify()。
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* 在有關Java的線程機制的討論中,有一個令人困惑的描述:notify()將喚醒“所有正在等待的任務”。這是否意味着在程序
* 中任何地方,任何處於wait()狀態中的任務都將被任何對notifyAll()的調用喚醒呢?在下面的示例中,與Task2相關的
* 代碼說明了情況並非如此---事實上,當notifyAll()因某個特定鎖而被調用時,只有等待這個鎖的任務纔會被喚醒。
*
*
* @create @author Henry @date 2016-12-21
*
*/
class Blocker {
private String name;
public Blocker(String name) {
this.name = name;
}
synchronized void waitingCall() {
try {
while (!Thread.interrupted()) {
wait();
System.out.println(Thread.currentThread() + " " + name);
}
} catch (InterruptedException e) {
// OK to exit this way
System.out.println("InterruptedException");
}
}
synchronized void prod() {
notify();
}
synchronized void prodAll() {
notifyAll();
}
}
/**
* Task 有其自己的Blocker對象。
*
* @create @author Henry @date 2016-12-21
*
*/
class Task implements Runnable {
static Blocker blocker = new Blocker("Task");
@Override
public void run() {
blocker.waitingCall();
}
}
/**
* Task2 有其自己的Blocker對象。
*
* @create @author Henry @date 2016-12-21
*
*/
class Task2 implements Runnable {
// A separate Blocker object;
static Blocker blocker = new Blocker("Task2");
@Override
public void run() {
blocker.waitingCall();
}
}
/**
* Task和Task2每個都有其自己的Blocker對象,因此每個Task對象都會在Task.blocker上阻塞,而每個Task2都會在
* Taks2.blocker上阻塞。在main()中,java.util.Timer對象唄設置爲每4/10秒執行一次run()方法,而這個run()
* 方法將經由“激勵”方法交替地在Task.blocker上調用notify()和notifyAll()。
* 從輸出中你可以看到,即使存在Task2.blocker上阻塞的Task2對象,也沒有任何在Task.blocker上的notify()或
* notifyAll()調用會導致Task2對象被喚醒。於此類似,在main()的結尾,調用了timer的cancel(),即使計時器被撤銷了,
* 前5個任務也依然在運行,並仍舊在它們對Task.blocker.waitingCall()的調用中被阻塞。對Task2.blocker.prodAll()
* 的調用所產生的輸出不包括任何在Task.blocker中的鎖上等待任務。
* 如果你瀏覽Blocker中的prod()和prodAll(),就會發現這是有意義的。這些方法是synchronized的,這意味着它們將獲取自身
* 的鎖,因此當它們調用notify()或notifyAll()時,只在這個鎖上調用是符合邏輯的---因此,將只喚醒在等待這個特定鎖的任務。
* Blocker.waitingCall()非常簡單,以至於在本例中,你只需聲明for(;;)而不是while(!Thread.interrupted())就可以到達
* 相同的效果,因爲在本例中,由於異常而離開循環和通過檢查interrupted()標誌離開循環時沒有任何區別的---在兩種情況下都要
* 執行相同的代碼。但是事實上,這個示例選擇檢查interrupted(),因爲存在着兩種離開循環的方式。如果在以後的某個時刻,
* 你決定要循環中添加更多的代碼,那麼如果沒有覆蓋從這個循環中退出的這兩條路徑,就會產生引入錯誤的風險。
*
* @create @author Henry @date 2016-12-21
*
*/
public class NotifyVsNotifyAll {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++)
exec.execute(new Task());
exec.execute(new Task2());
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
boolean prod = true;
@Override
public void run() {
if (prod) {
System.out.println("\nnotify()");
Task.blocker.prod();
prod = false;
} else {
System.out.println("\nnotifyAll()");
Task.blocker.prodAll();
prod = true;
}
}
}, 400, 400);// Run every .4 second
TimeUnit.SECONDS.sleep(5);
timer.cancel();
System.out.println("\n Timer canceled");
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("Task2.blocker.prodAll()");
Task2.blocker.prodAll();
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("\n Shutting down");
exec.shutdownNow();
}
}