Java 多執行緒中,在目前的執行緒呼叫物件的wait()
方法可以讓目前的執行緒暫停執行,等到其他的執行緒呼叫該物件的notify()
或notifyAll()
方法後才會繼續執行。
wait()
方法定義在Object
類別,因此所有的物件都可呼叫此方法。
wait()
要在同步方法(synchronized method)或同步區塊(synchronized block)中呼叫,即以關鍵字synchronized
宣告的方法或區塊。
// synchronized block
synchronized(obj){
//...
obj.wait();
//...
}
// synchronized method
public synchronized void method() {
// ...
}
執行緒進入同步區塊後即擁有物件的監控(monitor),或稱該執行緒取得物件監控的鎖(lock)。若呼叫wait()
時執行緒並沒有該物件的監控則會拋出IllegalMonitorStateException
例外
執行緒呼叫wait()
後會釋放物件的鎖並進入等待區(wait set),接著系統排程中的其他執行緒開始競爭物件的鎖。等待區的執行緒必須在其他執行緒呼叫物件的notify()
或notifyAll()
方法,或經過指定的時間後才會重新進入排程。
等待區的執行緒在休眠狀態,不會進入排程,必須等到以下之一發生才會被喚醒。
- 其他的執行緒呼叫該物件的
notify()
方法,且在等待區的執行緒剛好是被通知的對象。 - 其他的執行緒呼叫該物件的
notifyAll()
方法。等待區中的所有執行緒皆會被通知並競爭物件的鎖。 - 其他的執行緒中斷(interrupt)了在等待中的執行緒
- 指定的等待時間已經結束。
等待區的執行緒被喚醒後,若取得物件的鎖便會重新進入排程,也就是回復可執行(runnable)狀態。當執行緒取回物件的鎖時,會從原來呼叫wait()
的位置繼續執行。
當等待中的執行緒被中斷(interrupt)時,會發生InterruptedException
例外。
當執行緒呼叫物件的wait()
,時,執行緒只是釋放該物件的鎖並進入等待區,但等待中的執行緒對其他物件仍然可以存取。
範例一,宣告一個Message
類別,其物件為執行緒要鎖定的對象,呼叫wait()
及notify()
方法。
public class Message {
private String msg;
public Message(String str){
this.msg = str;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
Waiter
為Runnable
的實作,此執行緒在同步區塊中呼叫Message
物件的wait()
並進入等待區,直到被Notifier
通知才會繼續執行。
public class Waiter implements Runnable{
private Message message;
public Waiter(Message message){
this.message = message;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
synchronized(message){
try{
System.out.println(threadName +" 開始等待");
message.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(threadName + " 受到通知");
System.out.println(threadName + " 處理訊息:" + message.getMsg());
}
}
}
Notifier
為Runnable
的實作,此執行緒在同步區塊中呼叫Message
的notify()
來通知等待區執行緒。
public class Notifier implements Runnable{
private Message message;
public Notifier(Message message){
this.message = message;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 開始");
try {
System.out.println(threadName + " sleep一秒");
Thread.sleep(1000);
synchronized(message){
message.setMsg("在notifier設定的新訊息");
message.notify();
// message.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " 結束");
}
}
測試
public class Test {
public static void main(String[] args) {
Message message = new Message("原本的訊息");
Waiter waiter = new Waiter(message);
new Thread(waiter,"waiter").start();
Waiter waiter1 = new Waiter(message);
new Thread(waiter1, "waiter1").start();
Notifier notifier = new Notifier(message);
new Thread(notifier, "notifier").start();
System.out.println("全部的執行緒已經開始執行");
}
}
執行結果如下
全部的執行緒已經開始執行
waiter1 開始等待
waiter 開始等待
notifier 開始
notifier sleep一秒
notifier 結束
waiter1 受到通知
waiter1 處理訊息:在notifier設定的新訊息
範例一呼叫notify()
時程式並沒有結束,waiter
仍在等待狀態,因為notify()
只會通知任一等待區中的執行緒,所以只有waiter1
收到通知並繼續執行,而waiter
則沒有其他執行緒去喚醒它。如果改用notifyAll()
則全部的執行續皆會被喚醒然後執行到結束。
範例二,執行緒Printer
用來印出Counter
的累加結果,進入同步區塊後先呼叫wait()
來釋放counter的鎖並進入等待區。
public class Printer implements Runnable{
private Counter counter;
public Printer(Counter counter){
this.counter = counter;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 開始");
synchronized(counter){
try {
counter.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(threadName + " total:" + counter.getTotal());
System.out.println(threadName + " 結束");
}
}
Counter
在同步區塊中累加完後會呼叫notify()
來通知等待counter的鎖的執行緒。
public class Counter implements Runnable{
private int total = 0;
private int times;
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " 開始");
try {
Thread.sleep(1000); // 確保Printer.run()中的counter.wait()先執行
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this){
for(int i = 1; i <= times ; i++){
total += i;
}
notify();
}
System.out.println(threadName + " 結束");
}
public void setCountTimes(int times){
this.times = times;
}
public int getTotal(){
return total;
}
}
測試
public class Test {
public static void main(String[] args) {
Counter counter = new Counter();
counter.setCountTimes(10);
Thread p = new Thread(new Printer(counter), "printer");
Thread c = new Thread(counter, "counter");
p.start();
c.start();
}
}
執行結果
counter 開始
printer 開始
counter 結束
printer total:55
printer 結束
範例二的執行順序可以用下圖來表示
沒有留言:
張貼留言