功能
sleep()
sleep()方法需要指定等待的時間,它可以讓當前正在執行的線程在指定的時間內暫停執行,進入阻塞狀態。可以讓其他同優先級或者高優先級的線程得到執行的機會,也可以讓低優先級的線程得到執行機會。但是sleep()方法不會釋放“鎖標誌”,也就是說如果有synchronized同步塊,其他線程仍然不能訪問共享數據。
wait()
wait()方法需要和notify()及notifyAll()兩個方法一起介紹,這三個方法用於協調多個線程對共享數據的存取,所以必須在synchronized語句塊內使用,也就是說,調用wait(),notify()和notifyAll()的任務在調用這些方法前必須擁有對象的鎖。注意,它們都是Object類的方法,而不是Thread類的方法。 除了使用notify()和notifyAll()方法,還可以使用帶毫秒參數的wait(long timeout)方法,效果是在延遲timeout毫秒後,被暫停的線程將被恢復到鎖標誌等待池。 wait(),notify()及notifyAll()只能在synchronized語句中使用,但是如果使用的是ReenTrantLock實現同步,該如何達到這三個方法的效果呢?解決方法是使用ReenTrantLock.newCondition()獲取一個Condition類對象,然後Condition的await(),signal()以及signalAll()分別對應上面的三個方法。
yield()
yield()方法和sleep()方法類似,也不會釋放“鎖標誌”,區別在於,它沒有參數,即yield()方法只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態後馬上又被執行,另外yield()方法只能使同優先級或者高優先級的線程得到執行機會,這也和sleep()方法不同。
join()
join()方法會使當前線程等待調用join()方法的線程結束後才能繼續執行.
比較
sleep() vs wait()
來自不同的類
這兩個方法來自不同的類,sleep是Thread類的方法,而wait是Object類的方法
//Object.java public class Object { public native int hashCode() public boolean equals(Object obj) public String toString() public final native void notify(); public final native void notifyAll(); public final void wait() }
//Thread class Thread implements Runnable { public static native void yield(); public static native void sleep(long millis); ... }
釋放鎖
執行sleep方法後不會釋放鎖,而執行wait方法後會釋放鎖.
package com.hit.learn.concurrencyinaction; public class TestD { public static void main(String[] args) { new Thread(new Thread1()).start(); try { Thread.sleep(1000); System.out.println("main thread is work:1s, Thread1 is run and hold lock!"); } catch (Exception e) { e.printStackTrace(); } new Thread(new Thread2()).start(); } private static class Thread1 implements Runnable { @Override public void run() { System.out.println("Thread1 started!"); synchronized (TestD.class) { try { Thread.sleep(5000); //TestD.class.wait(); } catch (Exception e) { e.printStackTrace(); } System.out.println("thread1 is over!!!"); } } } private static class Thread2 implements Runnable { @Override public void run() { System.out.println("Thread2 started!"); synchronized (TestD.class) { System.out.println("Thread2 hold Lock"); //TestD.class.notify(); try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } System.out.println("thread2 is going on...."); System.out.println("thread2 is over!!!"); } } } }
執行結果如下:
Thread1 started! main thread is work:1s, Thread1 is run and hold lock! Thread2 started! thread1 is over!!! Thread2 hold Lock thread2 is going on.... thread2 is over!!!
Thread1先啓動,sleep了5s.在這期間啓動Thread2 是不能獲得鎖的,會在Thread2中的 synchronized (TestD.class) {} Block住,等待Thread1釋放掉lock之後,才能獲得鎖繼續執行.
package com.hit.learn.concurrencyinaction; public class TestD { public static void main(String[] args) { new Thread(new Thread1()).start(); try { Thread.sleep(1000); System.out.println("main thread is work:1s, Thread1 is run and hold lock!"); } catch (Exception e) { e.printStackTrace(); } new Thread(new Thread2()).start(); } private static class Thread1 implements Runnable { @Override public void run() { System.out.println("Thread1 started!"); synchronized (TestD.class) { try { //Thread.sleep(5000); TestD.class.wait(); } catch (Exception e) { e.printStackTrace(); } System.out.println("thread1 is over!!!"); } } } private static class Thread2 implements Runnable { @Override public void run() { System.out.println("Thread2 started!"); synchronized (TestD.class) { System.out.println("Thread2 hold Lock"); TestD.class.notify(); try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } System.out.println("thread2 is going on...."); System.out.println("thread2 is over!!!"); } } } }
執行結果如下:
Thread1 started! main thread is work:1s, Thread1 is run and hold lock! Thread2 started! Thread2 hold Lock thread2 is going on.... thread2 is over!!! thread1 is over!!!
Thread1先啓動,wait()了5s.在這期間啓動Thread2 是不能獲得鎖的,不會在Thread2中的 synchronized (TestD.class) {} Block住,notify()之後可以繼續執行.
是否需要在同步塊中
wait,notify和notifyAll只能在同步方法或同步代碼塊中調用,而sleep可以在任何地方調用。
private static class Thread1 implements Runnable { @Override public void run() { System.out.println("Thread1 started!"); try { TestD.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }
sleep() vs yield()
- sleep()方法給其他線程運行機會時不考慮其他線程的優先級,因此會給低優先級的線程運行的機會;yield()方法只會給相同優先級或更高優先級的線程運行的機會。
- 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態。
- sleep()方法聲明拋出InterruptedException異常,而yield()方法沒有聲明任何異常。
- sleep()方法比yield()方法具有更好的可移植性(跟操作系統CPU調度相關)。 sleep方法需要參數,而yield方法不需要參數。
notify() vs notifyAll()
先說兩個概念:鎖池和等待池
- 鎖池:假設線程A已經擁有了某個對象(注意:不是類)的鎖,而其它的線程想要調用這個對象的某個synchronized方法(或者synchronized塊),由於這些線程在進入對象的synchronized方法之前必須先獲得該對象的鎖的擁有權,但是該對象的鎖目前正被線程A擁有,所以這些線程就進入了該對象的鎖池中。
- 等待池:假設一個線程A調用了某個對象的wait()方法,線程A就會釋放該對象的鎖後,進入到了該對象的等待池中。
然後再來說notify和notifyAll的區別:
- 如果線程調用了對象的wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。
- 當有線程調用了對象的 notifyAll()方法(喚醒所有 wait 線程)或 notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調用了notify後只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內的所有線程移動到鎖池中,等待鎖競爭優先級高的線程競爭到對象鎖的概率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調用 wait()方法,它纔會重新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了 synchronized 代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。
綜上,所謂喚醒線程,另一種解釋可以說是將線程由等待池移動到鎖池,notifyAll調用後,會將全部線程由等待池移到鎖池,然後參與鎖的競爭,競爭成功則繼續執行,如果不成功則留在鎖池等待鎖被釋放後再次參與競爭。而notify只會喚醒一個線程。有了這些理論基礎,後面的notify可能會導致死鎖,而notifyAll則不會的例子也就好解釋了。