深入理解Java多執行緒中的wait(),notify()和sleep()

深入理解Java多執行緒中的wait(),notify()和sleep()

大家在學習Java的過程中,勢必要進行多執行緒的系統學習,這部分內容知識對於你在工作中的影響是極大的,並且在面試的過程中,這部分知識也是必然會被問到的。既然多執行緒的知識如此重要,那麼我們就不能淺嘗輒止。在這篇文章中,我想通過閱讀原始碼的方式給大家分享一下我自己對於Java中wait(),notify()和sleep()的理解,並且在後面我也做一些內容上的擴充套件,與大家共同思考。如果在文章中有描述不妥或者邏輯錯誤的地方,歡迎大家指正,謝謝!

wait()和sleep()

我們先簡單的瞭解一下wait()和sleep()這兩個方法:

首先wait()是屬於Object類的方法,從原始碼給出的解釋來看,wait()方法可以做到如下幾點:

(1)首先,呼叫了wait()之後會引起當前執行緒處於等待狀狀態。

(2)其次,每個執行緒必須持有該物件的monitor。如果在當前執行緒中呼叫wait()方法之後,該執行緒就會釋放monitor的持有物件並讓自己處於等待狀態。

(3)如果想喚醒一個正在等待的執行緒,那麼需要開啟一個執行緒通過notify()或者notifyAll()方法去通知正在等待的執行緒獲取monitor物件。如此,該執行緒即可打破等待的狀態繼續執行程式碼。

/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object.
* In other words, this method behaves exactly as if it simply
* performs the call {@code wait(0)}.
* <p>
* The current thread must own this object's monitor. The thread
* releases ownership of this monitor and waits until another thread
* notifies threads waiting on this object's monitor to wake up
* either through a call to the {@code notify} method or the
* {@code notifyAll} method. The thread then waits until it can
* re-obtain ownership of the monitor and resumes execution.
* <p>
* As in the one argument version, interrupts and spurious wakeups are
* possible, and this method should always be used in a loop:
* <pre>
*     synchronized (obj) {
*         while (<condition does not hold>)
*             obj.wait();
*         ... // Perform action appropriate to condition
*     }
* </pre>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @exception  IllegalMonitorStateException  if the current thread is not
*               the owner of the object's monitor.
* @exception  InterruptedException if any thread interrupted the
*             current thread before or while the current thread
*             was waiting for a notification.  The <i>interrupted
*             status</i> of the current thread is cleared when
*             this exception is thrown.
* @see        java.lang.Object#notify()
* @see        java.lang.Object#notifyAll()
*/
public final void wait() throws InterruptedException {
wait(0);
}

程式碼演示:

public class Main {
public static void main(String[] args) {
Main main = new Main();
main.startThread();
}
/**
* 執行緒鎖
*/
private final Object object = new Object();
/**
* 啟動執行緒
*/
public void startThread() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("開始執行執行緒。。。");
System.out.println("進入等待狀態。。。");
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("執行緒結束。。。");
}
});
t.start();
}
}

從程式碼來看,在執行執行緒和執行緒結束之間,我們先讓該執行緒獲取object物件作為自己的object’s monitor,然後呼叫了object物件的wait()方法從而讓其進入等待狀態。那麼程式執行的結果如下:

程式在未被喚醒之後,將不再列印“執行緒結束”,並且程式無法執行完畢一直處於等待狀態。

sleep()方法來自於Thread類,從原始碼給出的解釋來看,sleep()方法可以做到如下幾點:

(1)首先,呼叫sleep()之後,會引起當前執行的執行緒進入暫時中斷狀態,也即睡眠狀態。

(2)其次,雖然當前執行緒進入了睡眠狀態,但是依然持有monitor物件。

(3)在中斷完成之後,自動進入喚醒狀態從而繼續執行程式碼。

 /**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
*
* @param  millis
*         the length of time to sleep in milliseconds
*
* @throws  IllegalArgumentException
*          if the value of {@code millis} is negative
*
* @throws  InterruptedException
*          if any thread has interrupted the current thread. The
*          <i>interrupted status</i> of the current thread is
*          cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;

程式碼演示:

public class Main {
public static void main(String[] args) {
Main main = new Main();
main.startThread();
}
/**
* 啟動執行緒
*/
public void startThread() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("開始執行執行緒。。。");
System.out.println("進入睡眠狀態。。。");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒結束。。。");
}
});
t.start();
}
}

從執行的結果來看,我們可以看出程式雖然在執行過程中中斷了3秒,但是在3秒結束之後依然會繼續執行程式碼,直到執行結束。在睡眠的期間內,執行緒會一直持有monitor物件。

那麼從以上的理論和實踐來分析,我們能得出如下結論:

(1)線上程的執行過程中,呼叫該執行緒持有monitor物件的wait()方法時,該執行緒首先會進入等待狀態,並將自己持有的monitor物件釋放。

(2)如果一個執行緒正處於等待狀態時,那麼喚醒它的辦法就是開啟一個新的執行緒,通過notify()或者notifyAll()的方式去喚醒。當然,需要注意的一點就是,必須是同一個monitor物件。

(3)sleep()方法雖然會使執行緒中斷,但是不會將自己的monitor物件釋放,在中斷結束後,依然能夠保持程式碼繼續執行。

notify()和notifyAll()

說完了wait()方法之後,我們接下來討論一下Object類中的另外兩個與wait()相關的方法。首先還是通過原始碼的方式讓大家先初步瞭解一下:
/**
* Wakes up a single thread that is waiting on this object's
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary and occurs at
* the discretion of the implementation. A thread waits on an object's
* monitor by calling one of the {@code wait} methods.
* <p>
* The awakened thread will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened thread will
* compete in the usual manner with any other threads that might be
* actively competing to synchronize on this object; for example, the
* awakened thread enjoys no reliable privilege or disadvantage in being
* the next thread to lock this object.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. A thread becomes the owner of the
* object's monitor in one of three ways:
* <ul>
* <li>By executing a synchronized instance method of that object.
* <li>By executing the body of a {@code synchronized} statement
*     that synchronizes on the object.
* <li>For objects of type {@code Class,} by executing a
*     synchronized static method of that class.
* </ul>
* <p>
* Only one thread at a time can own an object's monitor.
*
* @exception  IllegalMonitorStateException  if the current thread is not
*               the owner of this object's monitor.
* @see        java.lang.Object#notifyAll()
* @see        java.lang.Object#wait()
*/
public final native void notify();

先來看下notify()這個方法,通過閱讀原始碼我們可以總結一下幾點:

(1)當一個執行緒處於wait()狀態時,也即等待它之前所持有的object’s monitor被釋放,通過notify()方法可以讓該執行緒重新處於活動狀態,從而去搶奪object’s monitor,喚醒該執行緒。
(2)如果多個執行緒同時處於等待狀態,那麼呼叫notify()方法只能隨機喚醒一個執行緒。
(3)在同一時間內,只有一個執行緒能夠獲得object’s monitor,執行完畢之後,則再將其釋放供其它執行緒搶佔。
當然,如何使執行緒成為object‘s monitor的持有者,我會在多執行緒的其他部落格中講解。
接下來,我們再來看看notifyAll()方法:
/**
* Wakes up all threads that are waiting on this object's monitor. A
* thread waits on an object's monitor by calling one of the
* {@code wait} methods.
* <p>
* The awakened threads will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened threads
* will compete in the usual manner with any other threads that might
* be actively competing to synchronize on this object; for example,
* the awakened threads enjoy no reliable privilege or disadvantage in
* being the next thread to lock this object.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @exception  IllegalMonitorStateException  if the current thread is not
*               the owner of this object's monitor.
* @see        java.lang.Object#notify()
* @see        java.lang.Object#wait()
*/
public final native void notifyAll();

那麼顧名思義,notifyAll()就是用來喚醒正在等待狀態中的所有執行緒的,不過也需要注意以下幾點:

(1)notifyAll()只會喚醒那些等待搶佔指定object’s monitor的執行緒,其他執行緒則不會被喚醒。
(2)notifyAll()只會一個一個的喚醒,而並非統一喚醒。因為在同一時間內,只有一個執行緒能夠持有object’s monitor
(3)notifyAll()只是隨機的喚醒執行緒,並非有序喚醒。
那麼如何做到有序喚醒是我們接下來要討論的問題。

notify()實現有序喚醒的思路和實現

就上節提出的問題,我們在這節中可以進行一下思考和討論。
首先,簡單來說,我們需要去解決的其實就是對於多執行緒針對object’s monitor的有序化。那麼根據這一思路,我直接上程式碼:
public class MyThreadFactory {
// 執行緒A是否處於等待狀態的標誌
private boolean isThreadAWaiting;
// 執行緒B是否處於等待狀態的標誌
private boolean isThreadBWaiting;
// 執行緒C是否處於等待狀態的標誌
private boolean isThreadCWaiting;
public MyThreadFactory() {
isThreadAWaiting = true;
isThreadBWaiting = true;
isThreadCWaiting = true;
}
/**
* 物件鎖
*/
private final Object object = new Object();
/**
* 該執行緒作為一個喚醒執行緒
*/
public void startWakenThread() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("喚醒執行緒開始執行...");
// 首先釋放執行緒A
quitThreadA();
}
}
});
t.start();
}
/**
* 啟動執行緒A
*/
public void startThreadA() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("執行緒A開始等待...");
try {
for (; ; ) {
if (!isThreadAWaiting) break;
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒A結束...");
// 執行緒A結束後,暫停2秒釋放執行緒B
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
quitThreadB();
}
}
});
t.start();
}
/**
* 啟動執行緒B
*/
public void startThreadB() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("執行緒B開始等待...");
try {
for (; ; ) {
if (!isThreadBWaiting) break;
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒B結束...");
// 執行緒B結束後,暫停2秒釋放執行緒C
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
quitThreadC();
}
}
});
t.start();
}
/**
* 啟動執行緒C
*/
public void startThreadC() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("執行緒C開始等待...");
try {
for (; ; ) {
if (!isThreadCWaiting) break;
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒C結束...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有執行緒執行完畢!");
}
}
});
t.start();
}
/**
* 執行緒A退出等待
*/
private void quitThreadA() {
isThreadAWaiting = false;
object.notify();
}
/**
* 執行緒B退出等待
*/
private void quitThreadB() {
isThreadBWaiting = false;
object.notify();
}
/**
* 執行緒C退出等待
*/
private void quitThreadC() {
isThreadCWaiting = false;
object.notify();
}
}

在以上程式碼中,我寫了三個執行緒A,B,C用來作為等待執行緒,並且最後通過一個喚醒執行緒來喚醒這三個執行緒。

我的思路是這樣的:
(1)通過notify()方法可以保證每次只喚醒一個執行緒,但是不能確保喚醒的是哪個執行緒。
(2)線上程A,B,C中,新增for或者while迴圈的方式使其進入無限等待的狀態。這樣能夠保證notify()無論如何都不能喚醒執行緒。
(3)分別給A,B,C執行緒設定各自的標記,如果要喚醒該執行緒的話,就改變其狀態並且跳出死迴圈,在最後執行下一個執行緒。
那麼最終呼叫的main函式如下:
public static void main(String[] args) {
MyThreadFactory factory = new MyThreadFactory();
factory.startThreadA();
factory.startThreadB();
factory.startThreadC();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
factory.startWakenThread();
}

最後執行的結果如下:

小結

本章是為了通過wait(),sleep(),notify()以及notifyAll()去了解它們是如何使用的,以及它們各自的意義。現在在java.util.concurrent包中,已經有很多針對多執行緒併發的執行緒池封裝類和介面,大家使用起來會更加靈活和方便,並且能夠實現更多併發效果。因此不太推薦大家使用wait()和notify()這種方式。在接下來的部落格中,我還會繼續深入多執行緒的知識,給大家帶來更多更深入的東西。希望大家給予支援。