wait & notify
wait
wait() 方法的作用是使目前執行的線程進入等待,代碼執行到 wait 一行進入等待;當線程被喚醒時從 wait 下一行開始執行。
wait() 方法需要在 synchronized 代碼塊中調用,否則會報錯。
wait() 方法會釋放鎖,其它線程可以競争獲得鎖
wait() 方法有一個帶時間參數的,當時間到了可以自動喚醒而不需要notify
notify
notify() 方法的作用是用來通知其它在等待同一把鎖的線程,當使用 notify() 方法時,會随機喚醒多個等待線程的其中一個,進行等待擷取該鎖。
notify() 方法需要在 synchronized 代碼塊中調用,否則會報錯。
notify() 方法不會立馬釋放鎖,(wait 是馬上釋放鎖的),需要等整個代碼執行完畢,才會釋放鎖,被通知的線程也不能馬上擷取鎖,需等 notify 的方法執行完之後,釋放了鎖,被通知的鎖才能擷取鎖。
notifyAll() 通知所有等待同一把鎖的全部線程結束等待,進入可運作狀态,全部線程等待方法釋放鎖然後一起競争這把鎖。
看個例子
public class Tongxin {
public static Object lock = new Object();
public static void main(String[] args) {
Tongxin tx = new Tongxin();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized(lock) {
System.out.println(Thread.currentThread().getName() + "調用wait前" + new Date());
lock.wait();
System.out.println(Thread.currentThread().getName() + "調用wait後" + new Date());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "執行最後的操作" + new Date());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock){
System.out.println(Thread.currentThread().getName() + "調用notify前" + new Date());
lock.notify();
System.out.println(Thread.currentThread().getName() + "調用notify後" + new Date());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "執行最後的操作" + new Date());
}
}
}).start();
}
}
---運作結果
Thread-0調用wait前Sun Jun 09 12:11:25 CST 2019
Thread-1調用notify前Sun Jun 09 12:11:25 CST 2019
Thread-1調用notify後Sun Jun 09 12:11:25 CST 2019
Thread-1執行最後的操作Sun Jun 09 12:11:28 CST 2019
Thread-0調用wait後Sun Jun 09 12:11:28 CST 2019
Thread-0執行最後的操作Sun Jun 09 12:11:28 CST 2019
可以看到 線程1在調用 notify 之後并沒有馬上釋放掉鎖,而是執行完才釋放線程0 才能獲得鎖。
剛才發生了一個小插曲,很納悶 notify 還沒執行完,wait 線程已經獲得鎖并執行了。
sleep & wait
都會進入等待,但是 sleep 是不會釋放鎖的,而 wait 會釋放鎖。
後來才發現,Thread.sleep(3000) 放到 synchronized 塊外面去了,鎖已經釋放掉了,是以才會發生上面的現象。
上面有提到,wait 方法和 notify 方法是需要結合 synchronized 來使用的,如果沒有 synchronized 是會報錯的。因為需要先擷取到對象鎖,然後在使用該鎖來調用 wait 和 notify。
使用 Lock 對象實作線程間通信
wait 和 notify 是屬于 Object 這個超類的方法,是以任何對象都能調用。
Lock 中有個實作類叫 ReentranLock,在 ReentranLock 中可以借用 Condition 對象實作線程間通信。
Condition 是一個接口,await 方法相當于 Object 中的 wait;signal 方法相當于 Object 中的 notify;signalAll 相當于 Object 中的 notifyAll。
例子
public class Tongxin2 {
public static Lock reentranLock = new ReentrantLock();
public static Condition condition = reentranLock.newCondition();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
reentranLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "調用wait前" + new Date());
condition.await();
System.out.println(Thread.currentThread().getName() + "調用wait後" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentranLock.unlock();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
reentranLock.lock();
System.out.println(Thread.currentThread().getName() + "調用notify前" + new Date());
condition.signal();
System.out.println(Thread.currentThread().getName() + "調用notify後" + new Date());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "執行最後的操作" + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentranLock.unlock();
}
}
}).start();
}
}
--- 運作結果
Thread-0調用wait前Sun Jun 09 15:20:26 CST 2019
Thread-1調用notify前Sun Jun 09 15:20:26 CST 2019
Thread-1調用notify後Sun Jun 09 15:20:26 CST 2019
Thread-1執行最後的操作Sun Jun 09 15:20:29 CST 2019
Thread-0調用wait後Sun Jun 09 15:20:29 CST 2019
可以看到,用法還是很簡單,reentranLock.lock() 獲得鎖,reentranLock.unlock() 釋放鎖。
差別
就線程間通信而言,Object 中的 wait notify 和 Condition 中的 await signal 沒有多大差別。
隻是前者是結合 synchronized 來使用,後者是結合 Lock 來使用。