1. 程式人生 > >多執行緒之等待通知機制

多執行緒之等待通知機制

[TOC] ## 什麼是等待通知機制 在單執行緒中,要執行的操作需要滿足一定條件才能執行,可以把這個操作放在if語句塊中。 在多執行緒程式設計中,可能A執行緒的條件沒有滿足只是暫時的,稍後其他的執行緒B可能會更新條件使得A執行緒的條件得以滿足,可以將A執行緒暫停,直到它的條件得到滿足之後再將A執行緒喚醒 ``` Atomic{ while(條件不成立) { 等待 } 條件滿足後,當前執行緒被喚醒 } ``` ## 等待通知機制的實現 object類中的Wait方法可以使當前執行緒的程式碼暫停執行,直到接到通知或者被中斷為止 注意: (1)wait方法只能再同步程式碼塊中由鎖物件呼叫 (2)呼叫wait方法,當前執行緒會釋放鎖 ```java public class Text16_5 { public static void main(String[] args) throws InterruptedException { String text="hello"; System.out.println("同步前程式碼塊"); synchronized (text) { System.out.println("同步程式碼塊開始"); text.wait(); System.out.println("同步程式碼塊結束"); } System.out.println("全部結束"); } } ``` ![image-20210316211333325](https://img2020.cnblogs.com/blog/1391439/202103/1391439-20210317224600443-529295315.png) 因為呼叫了鎖物件的wait方法,會釋放鎖物件,處於等待的狀態,沒有被喚醒就會一直等待下去。 object類的notify方法可以喚醒執行緒,該方法也必須同步在程式碼塊中,由鎖物件呼叫,沒有使用鎖物件呼叫wait/notify會報出IIegalMonuitorStateExeption異常,如果由多個等待的執行緒,notify方法只能喚醒其中的一個,在同步程式碼塊中呼叫notify方法後,並不會立即釋放鎖物件,需要等當前同步程式碼塊執行完後才會釋放鎖物件,一般將notify放在同步程式碼塊最後。 ``` synchronized(鎖物件) { //執行修改保護條件的程式碼 //喚醒其他執行緒 鎖物件.notify(); } ``` ```java public class TextNotify { public static void main(String[] args) throws InterruptedException { String text="hello"; Thread t1=new Thread(new Runnable() { @Override public void run() { synchronized (text) { System.out.println("同步程式碼塊開始"); try { text.wait();//執行緒等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("同步程式碼塊結束"); } } }); Thread t2=new Thread(new Runnable() { @Override public void run() { synchronized (text) { System.out.println("執行緒開始喚醒"); text.notify(); System.out.println("執行緒結束喚醒"); } } }); t1.start();//開啟t1執行緒 t1等待 Thread.sleep(3000);//睡眠3秒 確保t1處於等待狀態 t2.start();//喚醒t1執行緒 } } ``` ![image-20210316214002434](https://img2020.cnblogs.com/blog/1391439/202103/1391439-20210317224600722-1648353032.png) ## notify不會立即釋放鎖物件 案例: ```java import java.util.ArrayList; import java.util.List; public class NotifyText2 { public static void main(String[] args) throws InterruptedException { List strings=new ArrayList<>(); Thread t1=new Thread(new Runnable() { @Override public void run() { synchronized (strings) { System.out.println("執行緒1開始等待"); try { strings.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("執行緒1被喚醒"); } } }); Thread t2=new Thread(new Runnable() { @Override public void run() { synchronized (strings) { for (int i = 0; i <10 ; i++) { strings.add("data"+i); System.out.println("執行緒2添加了"+(i+1)); if(strings.size()==5) { strings.notify(); System.out.println("執行緒2被喚醒"); } } } } }); t1.start(); Thread.sleep(1000); t2.start(); } } ``` 執行緒2的程式碼還沒有執行完畢,鎖沒有立即釋放依然在執行,需要等到所有程式碼塊全部執行完畢才釋放 ![image-20210316220423289](https://img2020.cnblogs.com/blog/1391439/202103/1391439-20210317224600965-1827028796.png) ## interrupt會中斷執行緒的等待 ```java public class InterruptText { private static final String name=new String(); public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(new Runnable() { @Override public void run() { synchronized (name) { try { System.out.println("同步程式碼塊開始"); name.wait(); System.out.println("同步程式碼塊結束"); } catch (InterruptedException e) { System.out.println("wait被中斷"+e); } } } }); t1.start(); Thread.sleep(2000); t1.interrupt(); } } ``` 原來鎖物件需要執行完同步程式碼塊才能釋放鎖物件,在執行過程如果遇到異常也會導致執行緒終止,釋放鎖物件。呼叫wait方法也會釋放鎖物件。 ![image-20210317202003190](https://img2020.cnblogs.com/blog/1391439/202103/1391439-20210317224601151-1624377630.png) ## notify與notifyAll的區別 notify一次只能喚醒一個,如果有多個執行緒都在等待,只能隨機喚醒其中的一個,想要喚醒所有等待執行緒需要呼叫notifyAll。 ```java public class InterruptText { private static final String name=new String(); public static void main(String[] args) throws InterruptedException { String str=new String(); NotifyAll notifyAll=new NotifyAll(str); NotifyAll notifyAl2=new NotifyAll(str); NotifyAll notifyAll3=new NotifyAll(str); notifyAll.setName("執行緒一"); notifyAl2.setName("執行緒二"); notifyAll3.setName("執行緒三"); notifyAll.start(); notifyAl2.start(); notifyAll3.start(); Thread.sleep(2000);//休眠兩秒 synchronized (str) { //str.notify();只能隨機喚醒一個 str.notifyAll();//喚醒全部執行緒 } }; static class NotifyAll extends Thread { private String name; private NotifyAll(String name) { this.name=name; } @Override public void run() { synchronized (name) { try { System.out.println(Thread.currentThread().getName()+"同步程式碼塊開始"); name.wait(); System.out.println(Thread.currentThread().getName()+"同步程式碼塊結束"); } catch (InterruptedException e) { System.out.println("wait被中斷"+e); } } } } } ``` ![image-20210317221240524](https://img2020.cnblogs.com/blog/1391439/202103/1391439-20210317224601349-809173761.png) 如果只調用一次notify()之惡能喚醒其中的一個執行緒,其他等待執行緒依然處於等待狀態,就錯過了通知訊號,這種現象稱之為訊號丟失。 ## wait(Long)的使用 帶有引數的Wait(Long)方法,在指定時間內沒有操作會被自