爲了支持多線程之間的協做,JDK提供了兩個很是重要的接口線程等待wait()方法和通知notify()方法。這兩個方法並不在Thread類中,而是輸出Object類。這也就意味着任何對象均可以調用這兩個方法。java
public final void wait() throws InterruptedException {wait(0);} public final native void notify();
當在一個對象實例上調用wait()方法後,當前線程就會在這個對象上等待。這是什麼意思呢?好比線程A中,調用了obj.wait()方法,那麼線程A就會中止繼續執行,而轉爲等待狀態。等待到什麼時候結束呢?線程A會一直等到其餘線程調用了obj.notify()方法爲止。這時obj對象就儼然成爲多個線程之間的有效通信手段。多線程
這裏還須要強調一點,Object.wait()方法並非能夠隨便調用。它必須包含在對應的synchronzied語句中,而且都須要先得到目標對象的一個監視器。顯示了wait()與notify()的工做流程細節。其中T1和T2表示兩個線程。T1在正確執行wait()方法前,首先必須得到object對象的監視器,執行後會釋放這個監視器。爲了方便你們理解,這裏給出一個簡單的例子:ide
public class ThreadTest3 { final static Object obj = new Object(); public static class T1 extends Thread{ public void run(){ synchronized (obj){ System.out.println(System.currentTimeMillis()+":T1 Start!"); try { System.out.println(System.currentTimeMillis()+":T1 wait for object"); obj.wait(); }catch (Exception e){ e.printStackTrace(); } System.out.println(System.currentTimeMillis()+":T1 end"); } } } public static class T2 extends Thread{ public void run(){ synchronized (obj){ System.out.println(System.currentTimeMillis()+":T2 Start!"); System.out.println(System.currentTimeMillis()+":T2 wait for object"); obj.notify(); System.out.println(System.currentTimeMillis()+":T2 end"); try { Thread.sleep(20000); }catch (Exception e){ e.printStackTrace(); } } } } public static void main(String[] args) { Thread t1 = new T1(); Thread t2 = new T2(); t1.start(); t2.start(); } }
執行結果以下所示:spa
1496717793778:T1 Start! 1496717793778:T1 wait for object 1496717793779:T2 Start! 1496717793779:T2 wait for object 1496717793779:T2 end 1496717813779:T1 end
從程序打印的時間能夠到出,在T2通知T1繼續執行後,T1並不能當即繼續執行,而是要等待T2釋放obj的鎖,並從新成功獲取鎖後,才能繼續執行。所以T2休息了20秒。線程
注意:object.wait()和Thread.sleep()方法均可以讓線程等待若干時間,除了wait()能夠被喚醒外,另一個主要區別就是wait()方法會釋放目標對象鎖,而是Thread.sleep()方法不會釋聽任何資源。接下來要介紹code
的suspend也不會釋聽任何鎖資源。對象
不推薦使用suspend()去掛起線程的緣由,是由於suspend()在致使縣城暫停的同時,並不會釋聽任何鎖資源。此時,其餘線程想要訪問被它暫用的鎖時,都會沒法正常繼續運行。直到對應的線程進行了resume()操做,被掛起的線程才能繼續執行。在此不進行詳細描述。接口
隔壁的一時候,一個線程的輸入可能很是依賴於另一個或是多個線程的輸出,此時,這個線程就須要等待依賴線程執行完畢,才能繼續執行。JDK提供了join()操做來實現這個功能,以下資源
//此方法表示無限等待,它會一直阻塞當前線程,直到目標線程執行完畢。 public final void join() throws InterruptedException {join(0);} //該方法給出一個最大等待時間,若是超過給定時間目標線程還在執行,當前線程也會由於「等不及」而繼續往下執行。 public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
給出一個簡單點join()實例,給你們參考虛擬機
public class ThreadTestJoin { public volatile static int i=0; public static class addThread extends Thread{ public void run(){ for (i = 0; i < 1000000; i++); } } public static void main(String[] args) throws InterruptedException { addThread thread = new addThread(); thread.start(); System.out.println("等待前的:i"+i); thread.join(); System.out.println("等待後的:i"+i); } }
0 1000000
從上能夠看出,join()沒有等待addThread,i極可能是0或是一個很是小的數字。由於addThread尚未開始執行i的值就已經被輸出了。可是使用join()方法後,標識主線程願意等待addThread執行完畢,故join()返回時,addThread已經執行完畢,故i老是1000000。
另一個比較有趣的方法yield(),定義以下:
public static native void yield();
這是一個靜態方法,一旦執行,他會使當前線程讓出CPU。但要注意,讓出CPU並不表示當前線程不執行。當前線程在讓出CPU後,還會進行CPU資源的爭奪。可是是否可以再次被分配到,就不必定了。所以對yield()的調用就好像在說:我已經完成一些重要的工做了,我應該是能夠休息一下了,能夠給其餘線程一些工做機會啦。若是你以爲一個線程不那麼重要,或者優先級很是低,並且又懼怕它佔用太多的CPU資源,這時你能夠在適當時候調用yield()方法,給予其餘重要線程更多的工做機會。
上一個例子有用到volatile關鍵字,volatile英文解釋爲"異變的,不穩定的"這也正是這個關鍵詞的語義。當你用volatile去申明一個變量時,就等於你告訴了虛擬機,這個變量極有可能會被某些程序或線程所修改。爲了確保修改後的變量可讓全部線程都可以「看到」這個改動,虛擬機就必須採用一些特殊的手段來保證這個變量的可見性等特色。
public class DiscoveryApplicaion { public static volatile int i = 0; static class Plustask implements Runnable{ @Override public void run() { for (int k = 0; k < 1000; k++) { i++; } } } public static void main(String[] args) throws InterruptedException { Thread[] task = new Thread[10]; for (int j = 0; j <task.length ; j++) { task[j] = new Thread(new Plustask()); task[j].start(); task[j].join(); } // for (int j = 0; j < task.length; j++) { // task[j].join(); // } System.out.println(i); } }
執行上述代碼,若是i++是原子性的,那麼最終的值應該是10000(10個線程各累加1000次)。但實際上放開註釋輸出老是會小於10000。