前幾篇複習了下線程的建立方式、線程的狀態、Thread 的源碼這幾篇文章,這篇講講 Object 幾個跟線程獲取釋放鎖相關的方法:wait、notify、notifyAll。java
因爲 wait() 是 Object 類的 native 方法,在 idea 中,它長這樣:安全
public final native void wait(long timeout) throws InterruptedException;
看不了源碼,那隻能看源碼的註釋,註釋太長,我摘取一些關鍵的點出來:app
一、 Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed. The current thread must own this object's monitor. 二、 In other words,waits should always occur in loops.like this one: synchronized(obj) { while (condition does not hold) obj.wait(timeout); // Perform action appropriate to condition } 三、 @throws IllegalArgumentException if the value of timeout isnegative. @throws IllegalMonitorStateException if the current thread is notthe owner of the object 's monitor. @throws InterruptedException if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
註釋中提到幾點:ide
wait 會讓當前線程進入等待狀態,除非其餘線程調用了 notify 或者 notifyAll 方法喚醒它,又或者等待時間到。另外,當前線程必須持有對象監控器(也就是使用 synchronized 加鎖)oop
必須把 wait 方法寫在 synchronized 保護的 while 代碼塊中,並始終判斷執行條件是否知足,若是知足就往下繼續執行,若是不知足就執行 wait 方法,而在執行 wait 方法以前,必須先持有對象的 monitor 鎖,也就是一般所說的 synchronized 加鎖。this
超時時間非法,拋 IllegalArgumentException 異常;不持有對象的 monitor 鎖,拋 IllegalMonitorStateException 異常;在等待期間被其餘線程中斷,拋出 InterruptedException 異常。idea
逆向思考下,沒有 synchronized 保護的狀況下,咱們使用會出現啥問題?先試着來模擬一個簡單的生產者消費者例子:線程
public class BlockingQueue { Queue<String> buffer = new LinkedList<String>(); // 生產者,負責往隊列放數據 public void give(String data) { buffer.add(data); notify(); } // 消費者,主要是取數據 public String take() throws InterruptedException { while (buffer.isEmpty()) { wait(); } return buffer.remove(); } }
首先 give () 往隊列裏放數據,放完之後執行 notify 方法來喚醒以前等待的線程;take () 檢查整個 buffer 是否爲空,若是爲空就進入等待,若是不爲空就取出一個數據。但在這裏咱們並無用 synchronized 修飾。假設咱們如今只有一個生產者和一個消費者,那就有可能出現如下狀況:code
此時,生產者無數據。消費者線程調用 take(),while 條件爲 true。正常來講,這時應該去調用 wait() 等待,但此時消費者在調用 wait 以前,被被調度器暫停了,還沒來得及調用 wait。orm
到生產者調用 give 方法,放入數據並視圖喚醒消費者線程。可這個時候喚醒不起做用呀。消費者並無在等待。
最後,消費者回去調用 wait 方法,就進入了無限等待中。
看明白了嗎?第一步時,消費者判斷了 while 條件,但真正執行 wait 方法時,生產者已放入數據,以前的 buffer.isEmpty 的結果已通過期了,由於這裏的 「判斷 - 執行」 不是一個原子操做,它在中間被打斷了,是線程不安全的。
正確的寫法應該是這樣子的:如下寫法就確保永遠 notify 方法不會在 buffer.isEmpty 和 wait 方法之間被調用,也就不會有線程安全問題。
public class BlockingQueue { Queue<String> buffer = new LinkedList<String>(); // 生產者,負責往隊列放數據 public void give(String data) { synchronized(this) { buffer.add(data); notify(); } } // 消費者,主要是取數據 public String take() throws InterruptedException { synchronized(this) { while (buffer.isEmpty()) { wait(); } return buffer.remove(); } } }
notify & notifyAll 都是 Object 的 native 方法,在 IDEA 中看不到它的源碼,一樣是隻能看註釋。
public final native void notify(); public final native void notifyAll();
註釋中主要提到如下幾點:
兩點緣由:
Java 的每一個對象都有一把稱之爲 monitor 監視器的鎖,每一個對象均可以上鎖,因此在對象頭中有一個用來保存鎖信息的位置。這個鎖是對象級別的,而不是線程級別的,每一個對象都有鎖,經過線程得到。若是線程須要等待某些鎖那麼調用對象中的 wait 方法就有意義了,它等待的就是這個對象的鎖。若是 wait 方法定義在 Thread 類中,線程正在等待的是哪一個鎖就不明顯了。簡單來講,因爲 wait & notify & notifyAll 是鎖級別的操做,因此把他們定義在 Object 類中由於鎖屬於對象。
再者,若是把它們定義在 Thread 中,會帶來不少問題。一個線程能夠有多把鎖,你調用 wait 或者 notify,我怎麼知道你要等待的是哪把鎖?喚醒的哪一個線程呢?
上次的文章咱們已經看過了 sleep 的源碼了,它們的相同點主要有:
不一樣點: