一、sleep()
sleep()方法源碼:
/** * 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;
sleep()方法來自於Thread類,從源碼給出的解釋來看,sleep()方法可以做到如下幾點:
(1)sleep()使當前線程進入停滯狀態(阻塞當前線程),讓出CUP的使用、目的是不讓當前線程獨自霸佔該進程所獲的CPU資源,以留一定時間給其他線程執行的機會;
(2)sleep()是Thread類的Static(靜態)的方法;因此他不能改變對象的機鎖,所以當在一個Synchronized塊中調用Sleep()方法時,線程雖然休眠了,但是對象的機鎖並木有被釋放,其他線程無法訪問這個對象(即使睡著也持有對象鎖)。
(3)在sleep()休眠時間期滿後,該線程不一定會立即執行,這是因為其它線程可能正在運行而且沒有被調度為放棄執行,除非此線程具有更高的優先順序。
代碼示範:
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對象。
二、wait()
wait()方法源碼:
/** * 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); }
首先wait()是屬於Object類的方法,從源碼給出的解釋來看,wait()方法可以做到如下幾點:
(1)wait()方法是Object類裡的方法;當一個線程執行到wait()方法時,它就進入到一個和該對象相關的等待池中,同時失去(釋放)了對象的機鎖(暫時失去機鎖,wait(long timeout)逾時時間到後還需要返還對象鎖);其他線程可以訪問;
(2)每個線程必須持有該對象的monitor。如果在當前線程中調用wait()方法之後,該線程就會釋放monitor的持有對象並讓自己處於等待狀態。
(3)如果想喚醒一個正在等待的線程,那麼需要開啟一個線程通過notify()或者notifyAll()方法去通知正在等待的線程擷取monitor對象。如此,該線程即可打破等待的狀態繼續執行代碼。
(4)wiat()必須放在synchronized block中,否則會在program runtime時扔出”java.lang.IllegalMonitorStateException“異常。
代碼示範:
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()方法從而讓其進入等待狀態。那麼程式啟動並執行結果如下:
開始執行線程。。。進入等待狀態。。。
程式在未被喚醒之後,將不再列印“線程結束”,並且程式無法執行完畢一直處於等待狀態。
那麼從以上的理論和實踐來分析,我們能得出如下結論:
(1)線上程的運行過程中,調用該線程持有monitor對象的wait()方法時,該線程首先會進入等待狀態,並將自己持有的monitor對象釋放。
(2)如果一個線程正處於等待狀態時,那麼喚醒它的辦法就是開啟一個新的線程,通過notify()或者notifyAll()的方式去喚醒。當然,需要注意的一點就是,必須是同一個monitor對象。
(3)sleep()方法雖然會使線程中斷,但是不會將自己的monitor對象釋放,在中斷結束後,依然能夠保持代碼繼續執行。
三、notify()和notifyAll()
說完了sleep()和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(); }運行結果:
線程A開始等待...線程B開始等待...線程C開始等待...喚醒線程開始執行...線程A結束...線程B結束...線程C結束...所有線程執行完畢...