多執行緒之等待通知機制

炒燜煎糖板栗發表於2021-03-17

什麼是等待通知機制

在單執行緒中,要執行的操作需要滿足一定條件才能執行,可以把這個操作放在if語句塊中。

在多執行緒程式設計中,可能A執行緒的條件沒有滿足只是暫時的,稍後其他的執行緒B可能會更新條件使得A執行緒的條件得以滿足,可以將A執行緒暫停,直到它的條件得到滿足之後再將A執行緒喚醒

Atomic{
 while(條件不成立)
 {
 等待
 }
 條件滿足後,當前執行緒被喚醒
}

等待通知機制的實現

object類中的Wait方法可以使當前執行緒的程式碼暫停執行,直到接到通知或者被中斷為止

注意:

(1)wait方法只能再同步程式碼塊中由鎖物件呼叫

(2)呼叫wait方法,當前執行緒會釋放鎖

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

因為呼叫了鎖物件的wait方法,會釋放鎖物件,處於等待的狀態,沒有被喚醒就會一直等待下去。

object類的notify方法可以喚醒執行緒,該方法也必須同步在程式碼塊中,由鎖物件呼叫,沒有使用鎖物件呼叫wait/notify會報出IIegalMonuitorStateExeption異常,如果由多個等待的執行緒,notify方法只能喚醒其中的一個,在同步程式碼塊中呼叫notify方法後,並不會立即釋放鎖物件,需要等當前同步程式碼塊執行完後才會釋放鎖物件,一般將notify放在同步程式碼塊最後。

synchronized(鎖物件)
{
  //執行修改保護條件的程式碼
  //喚醒其他執行緒
  鎖物件.notify();
}
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

notify不會立即釋放鎖物件

案例:

import java.util.ArrayList;
import java.util.List;

public class NotifyText2 {
    public static void main(String[] args) throws InterruptedException {
        List<String> 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

interrupt會中斷執行緒的等待

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

notify與notifyAll的區別

notify一次只能喚醒一個,如果有多個執行緒都在等待,只能隨機喚醒其中的一個,想要喚醒所有等待執行緒需要呼叫notifyAll。

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

如果只呼叫一次notify()之惡能喚醒其中的一個執行緒,其他等待執行緒依然處於等待狀態,就錯過了通知訊號,這種現象稱之為訊號丟失。

wait(Long)的使用

帶有引數的Wait(Long)方法,在指定時間內沒有操作會被自動喚醒

相關文章