上一節講了Synchronized關鍵詞的原理與優化分析,而配合Synchronized使用的另外兩個關鍵詞wait¬ify是本章講解的重點。最簡單的東西,每每包含了最複雜的實現,由於須要爲上層的存在提供一個穩定的基礎,Object做爲Java中全部對象的基類,其存在的價值不言而喻,其中wait¬ify方法的實現多線程協做提供了保證。java
今天咱們要學習或者說分析的是 Object 類中的 wait¬ify 這兩個方法,其實說是兩個方法,這兩個方法包括他們的重載方法一共有 5 個,而 Object 類中一共才 12 個方法,可見這 2 個方法的重要性。咱們先看看 JDK 中的代碼:bash
public final native void notify();
public final native void notifyAll();
public final void wait() throws InterruptedException {
wait(0);
}
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
// 此處對於納秒的處理不精準,只是簡單增長了1毫秒,
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
複製代碼
就是這五個方法。其中有 3 個方法是 native 的,也就是由虛擬機本地的 c 代碼執行的。有 2 個 wait 重載方法最終仍是調用了 wait(long) 方法。多線程
wait方法:wait是要釋放對象鎖,進入等待池。既然是釋放對象鎖,那麼確定是先要得到鎖。因此wait必需要寫在synchronized代碼塊中,不然會報異常。ide
notify方法:也須要寫在synchronized代碼塊中,調用對象的這兩個方法也須要先得到該對象的鎖。notify,notifyAll,喚醒等待該對象同步鎖的線程。notify喚醒對象等待池中的一個線程,將這個線程放入該對象的鎖池中。對象的鎖池中線程能夠去競爭獲得對象鎖,而後開始執行。學習
- 若是是經過notify來喚起的線程,那先進入wait的線程會先被喚起來,並不是隨機喚醒;
- 若是是經過nootifyAll喚起的線程,默認狀況是最後進入的會先被喚起來,即LIFO的策略;
另外一點,notify,notifyAll調用時並不會釋放對象鎖。好比如下代碼:優化
public void test() {
Object object = new Object();
synchronized (object){
object.notifyAll();
while (true){
}
}
}
複製代碼
雖然調用了notifyAll,可是緊接着進入了一個死循環。致使一直不能出臨界區,一直不能釋放對象鎖。因此,即便它把全部在等待池中的線程都喚醒放到了對象的鎖池中,可是鎖池中的全部線程都不會運行,由於他們都拿不到鎖。ui
簡單示例:spa
public class WaitNotifyCase {
public static void main(String[] args) {
final Object lock = new Object();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (lock) {
try {
System.out.println("thread A get lock");
TimeUnit.SECONDS.sleep(1);
System.out.println("thread A do wait method");
lock.wait();
System.out.println("wait end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread B is waiting to get lock");
synchronized (lock) {
System.out.println("thread B get lock");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
System.out.println("thread B do notify method");
}
}
}).start();
}
}
複製代碼
執行結果:線程
thread A is waiting to get lock
thread A get lock
thread B is waiting to get lock
thread A do wait method
thread B get lock
thread B do notify method
wait end
複製代碼
前提:必須由同一個lock對象調用wait、notify方法code
- 當線程A執行wait方法時,該線程會被掛起;
- 當線程B執行notify方法時,會喚醒一個被掛起的線程A;
lock對象、線程A和線程B三者是一種什麼關係?根據上面的結論,能夠想象一個場景:
- lock對象維護了一個等待隊列list;
- 線程A中執行lock的wait方法,把線程A保存到list中;
- 線程B中執行lock的notify方法,從等待隊列中取出線程A繼續執行;