溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中怎么使用wait和notify實現(xiàn)線程間的通信

發(fā)布時間:2022-04-12 13:47:50 來源:億速云 閱讀:185 作者:iii 欄目:開發(fā)技術(shù)

這篇“Java中怎么使用wait和notify實現(xiàn)線程間的通信”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Java中怎么使用wait和notify實現(xiàn)線程間的通信”文章吧。

    一. 為什么需要線程通信

    線程是并發(fā)并行的執(zhí)行,表現(xiàn)出來是線程隨機執(zhí)行,但是我們在實際應(yīng)用中對線程的執(zhí)行順序是有要求的,這就需要用到線程通信

    線程通信為什么不使用優(yōu)先級來來解決線程的運行順序?

    總的優(yōu)先級是由線程pcb中的優(yōu)先級信息和線程等待時間共同決定的,所以一般開發(fā)中不會依賴優(yōu)先級來表示線程的執(zhí)行順序

    看下面這樣的一個場景:面包房的例子來描述生產(chǎn)者消費者模型

    有一個面包房,里面有面包師傅和顧客,對應(yīng)我們的生產(chǎn)者和消費者,而面包房有一個庫存用來存儲面包,當(dāng)庫存滿了之后就不在生產(chǎn),同時消費者也在購買面包,當(dāng)庫存面包賣完了之后,消費者必須等待新的面包生產(chǎn)出來才能繼續(xù)購買

    分析:對于何時停止生產(chǎn)何時停止消費就需要應(yīng)用到線程通信來準確的傳達生產(chǎn)和消費信息

    二. wait和notify方法

    wait():讓當(dāng)前線程持有的對象鎖釋放并等待

    wait(long timeout):對應(yīng)的參數(shù)是線程等待的時間

    notify():喚醒使用同一個對象調(diào)用wait進入等待的線程,重新競爭對象鎖

    notifyAll():如果有多個線程等待,notifyAll是全部喚醒 ,notify是隨機喚醒一個

    注意:

    這幾個方法都屬于Object類中的方法

    必須使用在synchronized同步代碼塊/同步方法中

    哪個對象加鎖,就是用哪個對象wait,notify

    調(diào)用notify后不是立即喚醒,而是等synchronized結(jié)束以后,才喚醒

    1. wait()方法

    調(diào)用wait方法后: 

    使執(zhí)行當(dāng)前代碼的線程進行等待(線程放在等待隊列)

    釋放當(dāng)前的鎖

    滿足一定條件時被喚醒,重新嘗試獲取鎖

    wait等待結(jié)束的條件:

    其他線程調(diào)用該對象的notify方法

    wait等待時間超時(timeout參數(shù)來指定等待時間)

    其他線程調(diào)用interrupted方法,導(dǎo)致wait拋出InterruptedException異常

    2. notify()方法 

    當(dāng)使用wait不帶參數(shù)的方法時,喚醒線程等待就需要使用notify方法

    這個方法是喚醒那些等待該對象的對象鎖的線程,使他們可以重新獲取該對象的對象鎖 

    如果有多個線程等待,則由線程調(diào)度器隨機挑選出一個呈wait 狀態(tài)的線程(不存在先來后到)

    在notify()方法后,當(dāng)前線程不會馬上釋放該對象鎖,要等到執(zhí)行notify()方法的線程將程序執(zhí)行完,也就是退出同步代碼塊之后才會釋放對象鎖

    3. notifyAll()方法

    該方法和notify()方法作用一樣,只是喚醒的時候,將所有等待的線程都喚醒

    notify()方法只是隨機喚醒一個線程 

    三. 使用wait和notify實現(xiàn)面包房業(yè)務(wù) 

    前提說明:

    有2個面包師傅,面包師傅一次可以做出兩個面包

    倉庫可以存儲100個面包

    有10個消費者,每個消費者一次購買一個面包 

    注意:

    消費和生產(chǎn)是同時并發(fā)并行進行的,不是一次生產(chǎn)一次消費

    實現(xiàn)代碼:

    public class Bakery {
        private static int total;//庫存
     
        public static void main(String[] args) {
            Producer producer = new Producer();
            for(int i = 0;i < 2;i++){
                new Thread(producer,"面包師傅-"+(i-1)).start();
            }
            Consumer consumer = new Consumer();
            for(int i = 0;i < 10;i++){
                new Thread(consumer,"消費者-"+(i-1)).start();
            }
        }
        private static class Producer implements Runnable{
            private int num = 3; //生產(chǎn)者每次生產(chǎn)三個面包
            @Override
            public void run() {
                try {
                    while(true){ //一直生產(chǎn)
                        synchronized (Bakery.class){
                            while((total+num)>100){ //倉庫滿了,生產(chǎn)者等待
                                Bakery.class.wait();
                            }
                            //等待解除
                            total += num;
                            System.out.println(Thread.currentThread().getName()+"生產(chǎn)面包,庫存:"+total);
                            Thread.sleep(500);
                            Bakery.class.notifyAll(); //喚醒生產(chǎn)
                        }
                        Thread.sleep(500);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        private static class Consumer implements Runnable{
            private int num = 1; //消費者每次消費1個面包
            @Override
            public void run() {
                try {
                    while(true){ //一直消費
                        synchronized (Bakery.class){
                            while((total-num)<0){ //倉庫空了,消費者等待
                                Bakery.class.wait();
                            }
                            //解除消費者等待
                            total -= num;
                            System.out.println(Thread.currentThread().getName()+"消費面包,庫存:"+total);
                            Thread.sleep(500);
                            Bakery.class.notifyAll(); //喚醒消費
                        }
                        Thread.sleep(500);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    部分打印結(jié)果:

    Java中怎么使用wait和notify實現(xiàn)線程間的通信

    四. 阻塞隊列

    阻塞隊列是一個特殊的隊列,也遵循“先進先出”的原則,它是線程安全的隊列結(jié)構(gòu)

    特性:典型的生產(chǎn)者消費者模型,一般用于做任務(wù)的解耦和消峰

    隊列滿的時候,入隊列就堵塞等待(生產(chǎn)),直到有其他線程從隊列中取走元素
    隊列空的時候,出隊列就堵塞等待(消費),直到有其他線程往隊列中插入元素 

    1. 生產(chǎn)者消費者模型 

    生產(chǎn)者消費者模式就是通過一個容器來解決生產(chǎn)者和消費者的強耦合問題

    生產(chǎn)者和消費者彼此之間不直接通信,而通過阻塞隊列來進行通信,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后等待消費者處理,直接扔給阻塞隊列,消費者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊列里取

    阻塞隊列就相當(dāng)于一個緩沖區(qū),平衡了生產(chǎn)者和消費者的處理能力
    阻塞隊列也能使生產(chǎn)者和消費者之間解耦

    上述面包房業(yè)務(wù)的實現(xiàn)就是生產(chǎn)者消費者模型的一個實例

    2. 標(biāo)準庫中的阻塞隊列

    在 Java 標(biāo)準庫中內(nèi)置了阻塞隊列, 如果我們需要在一些程序中使用阻塞隊列, 直接使用標(biāo)準庫中的即可

    BlockingQueue 是一個接口. 真正實現(xiàn)的類是 LinkedBlockingQueue

    put 方法用于阻塞式的入隊列, take 用于阻塞式的出隊列

    BlockingQueue 也有 offer, poll, peek 等方法, 但是這些方法不帶有阻塞特性

            BlockingDeque<String> queue = new LinkedBlockingDeque<>();
            queue.put("hello");
            //如果隊列為空,直接出出隊列就會阻塞
            String ret = queue.take();
            System.out.println(ret);

    3. 阻塞隊列的模擬實現(xiàn)

    這里使用數(shù)組實現(xiàn)一個循環(huán)隊列來模擬阻塞隊列

    當(dāng)隊列為空的時候,就不能取元素了,就進入wait等待,當(dāng)有元素存放時,喚醒

    當(dāng)隊列為滿的時候,就不能存元素了,就進入wait等待,當(dāng)鈾元素取出時,喚醒 

    實現(xiàn)代碼:

    public class MyBlockingQueue {
        //使用數(shù)組實現(xiàn)一個循環(huán)隊列,隊列里面存放的是線程要執(zhí)行的任務(wù)
        private Runnable[] tasks;
        //隊列中任務(wù)的數(shù)量,根據(jù)數(shù)量來判斷是否可以存取
        private int count;
        private int putIndex; //存放任務(wù)位置
        private int takeIndex; //取出任務(wù)位置
     
        //有參的構(gòu)造方法,表示隊列容量
        public MyBlockingQueue(int size){
            tasks = new Runnable[size];
        }
     
        //存任務(wù)
        public void put(Runnable task){
            try {
                synchronized (MyBlockingQueue.class){
                    //如果隊列容量滿了,則存任務(wù)等待
                    while(count == tasks.length){
                        MyBlockingQueue.class.wait();
                    }
                    tasks[putIndex] = task; //將任務(wù)放入數(shù)組
                    putIndex = (putIndex+1) % tasks.length; //更新存任務(wù)位置
                    count++; //更新存放數(shù)量
                    MyBlockingQueue.class.notifyAll(); //喚醒存任務(wù)
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
     
        //取任務(wù)
        public Runnable take(){
            try {
                synchronized (MyBlockingQueue.class){
                    //如果隊列任務(wù)為空,則取任務(wù)等待
                    while(count==0){
                        MyBlockingQueue.class.wait();
                    }
                    //取任務(wù)
                    Runnable task = tasks[takeIndex];
                    takeIndex = (takeIndex+1) % tasks.length; //更新取任務(wù)位置
                    count--; //更新存放數(shù)量
                    MyBlockingQueue.class.notifyAll(); //喚醒取任務(wù)
                    return task;
                }
            } catch (InterruptedException e) {
               throw new RuntimeException("存放任務(wù)出錯",e);
            }
        }
    }

    五. wait和sleep的區(qū)別(面試題)

    相同點:

    都可以讓線程放棄執(zhí)行一段時間 

    不同點:

    ??wait用于線程通信,讓線程在等待隊列中等待

    ??sleep讓線程阻塞一段時間,阻塞在阻塞隊列中

    ??wait需要搭配synchronized使用,sleep不用搭配

    ??wait是Object類的方法,sleep是Thread的靜態(tài)方法

    以上就是關(guān)于“Java中怎么使用wait和notify實現(xiàn)線程間的通信”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI