本文主要學習JAVA多線程中的 wait()方法 與 notify()/notifyAll()方法的用法。html
目錄以下:java
wait() 與 notify/notifyAll() 是Object類的方法,在執行兩個方法時,要先得到鎖。那麼怎麼得到鎖呢?編程
JAVA多線程之Synchronized關鍵字--對象鎖的特色。文章中介紹了使用synchronized關鍵字得到鎖。所以,wait() 與 notify/notifyAll() 常常與synchronized搭配使用,即在synchronized修飾的同步代碼塊或方法裏面調用wait() 與 notify/notifyAll()方法。多線程
因爲 wait() 與 notify/notifyAll() 是放在同步代碼塊中的,所以線程在執行它們時,確定是進入了臨界區中的,即該線程確定是得到了鎖的。ide
當線程執行wait()時,會把當前的鎖釋放,而後讓出CPU,進入等待狀態。學習
當執行notify/notifyAll方法時,會喚醒一個處於等待該 對象鎖 的線程,而後繼續往下執行,直到執行完退出對象鎖鎖住的區域(synchronized修飾的代碼塊)後再釋放鎖。測試
從這裏能夠看出,notify/notifyAll()執行後,並不當即釋放鎖,而是要等到執行完臨界區中代碼後,再釋放。故,在實際編程中,咱們應該儘可能在線程調用notify/notifyAll()後,當即退出臨界區。即不要在notify/notifyAll()後面再寫一些耗時的代碼。示例以下:this
public class Service { public void testMethod(Object lock) { try { synchronized (lock) { System.out.println("begin wait() ThreadName=" + Thread.currentThread().getName()); lock.wait(); System.out.println(" end wait() ThreadName=" + Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } } public void synNotifyMethod(Object lock) { try { synchronized (lock) { System.out.println("begin notify() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); lock.notify(); Thread.sleep(5000); System.out.println(" end notify() ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
在第3行的testMethod()中調用 wait(),在第17行的synNotifyMethod()中調用notify()線程
從上面的代碼能夠看出,wait() 與 notify/notifyAll()都是放在同步代碼塊中才可以執行的。若是在執行wait() 與 notify/notifyAll() 以前沒有得到相應的對象鎖,就會拋出:java.lang.IllegalMonitorStateException異常。code
在第8行,當ThreadA線程執行lock.wait();這條語句時,釋放得到的對象鎖lock,並放棄CPU,進入等待隊列。
當另外一個線程執行第23行lock.notify();,會喚醒ThreadA,可是此時它並不當即釋放鎖,接下來它睡眠了5秒鐘(sleep()是不釋放鎖的,事實上sleep()也能夠不在同步代碼塊中調用),直到第28行,退出synchronized修飾的臨界區時,纔會把鎖釋放。這時,ThreadA就有機會得到另外一個線程釋放的鎖,並從等待的地方起(第9行)起開始執行。
接下來是兩個線程類,線程類ThreadA調用testMethod()方法執行lock.wait();時被掛起,另外一個線程類synNotifyMethodThread調用synNotifyMethod()負責喚醒掛起的線程。代碼以下:
public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { Service service = new Service(); service.testMethod(lock); } } public class SynNotifyMethodThread extends Thread { private Object lock; public SynNotifyMethodThread(Object lock) { super(); this.lock = lock; } @Override public void run() { Service service = new Service(); service.synNotifyMethod(lock); } }
再接下來是測試類:
public class Test { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); //NotifyThread notifyThread = new NotifyThread(lock); // notifyThread.start(); SynNotifyMethodThread c = new SynNotifyMethodThread(lock); c.start(); } }
示例代碼以下:
public class Service { public void testMethod(Object lock) { try { synchronized (lock) { System.out.println("begin wait()"); lock.wait(); System.out.println(" end wait()"); } } catch (InterruptedException e) { e.printStackTrace(); System.out.println("出現異常"); } } } public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { Service service = new Service(); service.testMethod(lock); } }
注意,在第23行wait()方法是Object類的對象lock調用的。而下面的interrupt()方法是ThreadA類的對象調用的。在ThreadA裏面,將Object的對象做爲參數傳給了testMethod()方法,ThreadA的run()方法去調用testMethod(),從而wait()使ThreadA的線程暫停了(暫停當前執行wait()的線程)。從這裏能夠看出一個區別:
Object類中與線程有關的方法:
1)notify/notifyAll
2)wait()/wait(long)
java.lang.Thread中與之相關的方法:
1)interrupt()
2)sleep()/sleep(long)
3)join()/suspend()/resume()....
測試類代碼以下:
public class Test { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(5000); a.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
當執行第13行的interrupt()時,處於wait中的線程「當即」被喚醒(通常是當即響應中斷請求),並拋出異常。此時,線程也就結束了。
假設在線程A中執行wait(),在線程B中執行notify()。但若是線程B先執行了notify()而後結束了,線程A纔去執行wait(),那此時,線程A將沒法被正常喚醒了(還能夠經過③中提到的interrupt()方法以拋出異常的方式喚醒^~^)。
這篇文章: JAVA多線程之線程間的通訊方式中的第③點提到了notify通知順序出錯會致使 調用wait()進入等待隊列的線程再也沒法被喚醒了。
有兩個線程從List中刪除數據,而只有一個線程向List中添加數據。初始時,List爲空,只有往List中添加了數據以後,才能刪除List中的數據。添加數據的線程向List添加完數據後,調用notifyAll(),喚醒了兩個刪除線程,可是它只添加了一個數據,而如今有兩個喚醒的刪除線程,這時怎麼辦??
若是用 if 測試List中的數據的個數,則會出現IndexOutofBoundException,越界異常。緣由是,List中只有一個數據,第一個刪除線程把數據刪除後,第二個線程再去執行刪除操做時,刪除失敗,從而拋出 IndexOutofBoundException。
可是若是用while 測試List中數據的個數,則不會出現越界異常!!!神奇。
當wait等待的條件發生變化時,會形成程序的邏輯混亂---即,List中沒有數據了,再仍是有線程去執行刪除數據的操做。所以,須要用while循環來判斷條件的變化,而不是用if。
示例以下:Add類,負責添加數據:
public class Add { private String lock; public Add(String lock) { super(); this.lock = lock; } public void add() { synchronized (lock) { ValueObject.list.add("anyString"); lock.notifyAll(); } } } public class ThreadAdd extends Thread { private Add p; public ThreadAdd(Add p) { super(); this.p = p; } @Override public void run() { p.add(); } }
Subtract類,負責刪除數據----先要進行條件判斷,而後執行wait(),這意味着:wait等待的條件可能發生變化!!!
public class Subtract { private String lock; public Subtract(String lock) { super(); this.lock = lock; } public void subtract() { try { synchronized (lock) { if(ValueObject.list.size() == 0) {//將這裏的if改爲while便可保證不出現越界異常!!!! System.out.println("wait begin ThreadName=" + Thread.currentThread().getName()); lock.wait(); System.out.println("wait end ThreadName=" + Thread.currentThread().getName()); } ValueObject.list.remove(0); System.out.println("list size=" + ValueObject.list.size()); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class ThreadSubtract extends Thread { private Subtract r; public ThreadSubtract(Subtract r) { super(); this.r = r; } @Override public void run() { r.subtract(); } }
封裝的List隊列:
public class ValueObject { public static List list = new ArrayList(); }
測試類:
public class Run { public static void main(String[] args) throws InterruptedException { String lock = new String(""); Add add = new Add(lock); Subtract subtract = new Subtract(lock); ThreadSubtract subtract1Thread = new ThreadSubtract(subtract); subtract1Thread.setName("subtract1Thread"); subtract1Thread.start(); ThreadSubtract subtract2Thread = new ThreadSubtract(subtract); subtract2Thread.setName("subtract2Thread"); subtract2Thread.start(); Thread.sleep(1000); ThreadAdd addThread = new ThreadAdd(add); addThread.setName("addThread"); addThread.start(); } }