亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java多線程中的wait與notify方法詳解

 更新時(shí)間:2023年08月26日 08:59:45   作者:一只愛打拳的程序猿  
這篇文章主要介紹了Java多線程中的wait與notify方法詳解,線程的調(diào)度是無序的,但有些情況要求線程的執(zhí)行是有序的,因此,我們可以使用 wait() 方法來使線程執(zhí)行有序,需要的朋友可以參考下

 前言

我們知道,線程的調(diào)度是無序的,但有些情況要求線程的執(zhí)行是有序的。

因此,我們可以使用 wait() 方法來使線程執(zhí)行有序。

本期講解 Java 多線程中 synchronized 鎖配套使用的 wait 方法、notify方法和notifyAll方法,以及 wait 方法與 sleep 方法之間的區(qū)別、為什么要使用 wait 和 notify 方法。

為什么要使用wait()方法和notify()方法?

當(dāng)我們的 Java 代碼使用 synchronized 進(jìn)行加鎖時(shí),會出現(xiàn)線程之間搶占資源的情況。

這樣就會導(dǎo)致某一個(gè)線程不符合條件卻反復(fù)搶到資源,其他線程參與不了資源。

因此得使用 wait() 方法與 notify() 方法來解決該問題。

通過現(xiàn)實(shí)生活中的經(jīng)歷舉一例子:

把三個(gè)線程比做人,把一臺 ATM 機(jī)比作鎖(synchronized)。

當(dāng)這三個(gè)線程去取錢時(shí),線程1優(yōu)先進(jìn)入了 ATM 機(jī)里面取錢。

當(dāng) ATM 里面沒有錢時(shí),線程1就出了 ATM 機(jī)。但由于線程離開了 ATM 機(jī)后,會一直與線程2和線程3搶占 ATM 機(jī),因此會造成一個(gè)極端的后果,就是線程1一直進(jìn)入 ATM 機(jī)然后出 ATM 機(jī),并且一直循環(huán)下去。

以上例子,線程1發(fā)現(xiàn) ATM 沒錢可取,卻還是反復(fù)進(jìn)出 ATM 這樣這樣其他線程就無法嘗試取錢,對應(yīng)的就是多線程中的多個(gè)線程競爭鎖(synchroized)的情況,如何解決以上問題。

使用 wait 方法和 notify 方法。當(dāng) ATM(synchronized) 內(nèi)使用了 wait 方法,線程1取不了錢就會取消鎖狀態(tài)并且處于等待狀態(tài),當(dāng)其他線程進(jìn)入 ATM 機(jī)并且取到了錢這時(shí)候就可以使用 notify 方法喚醒 線程1的等待狀態(tài),那么線程1又可以進(jìn)行取錢操作,也就是進(jìn)行鎖的競爭。

在使用 wait 方法后,線程1發(fā)現(xiàn) ATM 里面沒有錢可取,就會通過 wait 方法來釋放鎖并且進(jìn)行阻塞等待(也就是暫時(shí)不參與 CPU 的調(diào)度、鎖的競爭),這個(gè)時(shí)候線程2和線程3就能很好的參與取錢這個(gè)操作了。

當(dāng)其他線程 使用 notify 方法時(shí),發(fā)現(xiàn) ATM 里面又有錢可取了。因此就會喚醒線程1的阻塞等待,這時(shí)線程1又可以參與 ATM(鎖) 的競爭。直到,所有的線程都取到錢為止。

那么使得上述三個(gè)線程能供協(xié)調(diào)的完成取錢這個(gè)工作,會用到三個(gè)方法:

  • wait() 方法/帶參數(shù)的wait()方法 - 讓當(dāng)前線程進(jìn)入等待阻塞狀態(tài)
  • notify() 方法 / notifyAll() 方法 - 喚醒當(dāng)前對象上等待的線程

注意:wait,notify、notifyAll都是 Object 類中的方法。

1. wait()方法

wait 方法使用后:會把當(dāng)前的執(zhí)行的線程進(jìn)行等待阻塞,然后釋放當(dāng)前線程的鎖狀態(tài),當(dāng)滿足了一定條件后就被喚醒,重新嘗試獲取這個(gè)鎖。

wait 結(jié)束條件的為:

  • 其他線程調(diào)用該對象的 notify 方法,
  • wait 等待時(shí)間超時(shí)(wait 方法提供了一個(gè)帶有參數(shù)的版本,可以指定等待時(shí)間)
  • 其他線程調(diào)用該等待的線程的 interrupt 方法,導(dǎo)致 wait 拋出 InterruptedException 異常。

解釋:interrupt(),在一個(gè)線程中調(diào)用另一個(gè)線程的interrupt()方法,即會向那個(gè)線程發(fā)出信號—線程中斷狀態(tài)已被設(shè)置。至于那個(gè)線程何去何從,由具體的代碼實(shí)現(xiàn)決定。

wait 和 notify 方法是 Object 類里面的方法,只要是一個(gè)類對象都能調(diào)用這兩個(gè)方法。因此,我們可以寫出以下代碼:

    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        System.out.println("Hello object");
        object.wait();
        System.out.println("object結(jié)束");
    }

運(yùn)行后打印:

以上代碼運(yùn)行后打印出一個(gè)非法的警告:非法的鎖狀態(tài)異常,因?yàn)?wait 方法必須要搭配 synchronized 來使用,脫離了 synchronized 的前提下 使用 wait 就會出現(xiàn)報(bào)錯(cuò)。

2. notify()方法

notify 方法是喚醒等待的線程,也就是喚醒調(diào)用了 wait 方法的線程。

notify 方法作用:

  • notify 方法也要在同步方法或同步塊中調(diào)用,該方法是用來通知那些可能等待該對象的對象鎖的
  • 其它線程,對其發(fā)出通知notify,并使它們重新獲取該對象的對象鎖
  • 如有多個(gè)線程處于等待,則線程調(diào)度器會隨機(jī)挑選一個(gè)調(diào)用 wait 狀態(tài)的線程。
  • 在調(diào)用 notify 方法后,當(dāng)前線程不會立馬釋放該對象的鎖,要等當(dāng)前調(diào)用 notify 方法的線程執(zhí)行完畢后,才會釋放該對象的鎖。

在理解 wait 方法和 notify 方法的作用以及使用方法后,下面我們來看下 wait 方法和 notify 方法的結(jié)合使用。 

3. wait()和notify()方法的使用

代碼案例:使用 notify() 方法喚醒 thread1線程。

  • 實(shí)例化一個(gè) Object 類的對象,調(diào)用 wait 和 notify 方法都是用該對象的引用 object 來調(diào)用。
  • 創(chuàng)建兩個(gè)線程:線程1和線程2,線程1執(zhí)行兩條語句,線程2也執(zhí)行兩條語句。
  • 線程1內(nèi)使用 object 來調(diào)用 wait 方法(兩條語句中間調(diào)用)
  • 線程2內(nèi)使用 object 來調(diào)用 notify 方法(兩條語句中間調(diào)用)

因此,有以下代碼:

    public static void main(String[] args) {
        Object object = new Object();
        Thread thread1 = new Thread(()-> {
            synchronized (object) {
                System.out.println("thread1開始");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread1結(jié)束");
            }
        });
        thread1.start();//啟動thread1線程
        Thread thread2 = new Thread(()-> {
            synchronized (object) {
                System.out.println("thread2開始");
                object.notify();
                System.out.println("thread2結(jié)束");
            }
        });
        thread2.start();//啟動thread2線程
    }

運(yùn)行后打印:

以上代碼,輸出順序與需求有所差異,但最終還是達(dá)到了效果。

造成輸出順序的不規(guī)則原因?yàn)椋?/p>

當(dāng) thread1 線程被 wait 前打印了語句“thread1開始”,thread2 線程 中調(diào)用了 notify 方法,這時(shí)會喚醒 thread1 線程,但是前提得執(zhí)行完 thread2 中的內(nèi)容“thread2開始”、“thread2結(jié)束”這兩個(gè)條語句。隨后才輸出被喚醒的 thread1 線程中的“thread1結(jié)束”語句。

當(dāng)然,既然這樣為啥我們不使用 join() 方法呢,thread1 線程完全執(zhí)行完畢,再執(zhí)行 thread2線程呢?具體情況具體分析,當(dāng)我們的代碼需求滿足使用 join() 方法時(shí),我們就使用 join() 方法。

對應(yīng)上述代碼,join() 方法會使 thread1 線程執(zhí)行完畢后再執(zhí)行 thread2 線程。而 wait() 和 notify() 方法會使 thread1 線程執(zhí)行一部分后,執(zhí)行 thread2 線程,執(zhí)行完 thread2 一部分代碼后,再執(zhí)行thread1 線程。這樣就滿足了特定的條件,類似于上文中線程取錢情況。大家可以自行嘗試一番。

注意,wait() 方法的初心就是為了等待、阻塞的效果。在 synchronized 內(nèi)調(diào)用 wait() 方法,得按 Alt+Enter 這兩個(gè)組合鍵來 try/catch 異常。

4. notifyAll()方法

notifyAll() 方法是用來喚醒當(dāng)前對象的所有調(diào)用 wait() 的線程。案例:

  • 有三個(gè)線程,線程1為thread1、線程2為thread2、線程3為thread3
  • thread1 中輸出兩條語句“thread1開始”、“thread1結(jié)束”
  • thread2 中輸出兩條語句“thread2開始”、“thread2結(jié)束”
  • thread1 和 threa2 在兩條語句中間通過 Object 類的引用調(diào)用 wait() 方法造成阻塞
  • thread3 線程通過 Object 類的引用調(diào)用 notifyAll() 方法喚醒所有的阻塞

因此,前兩個(gè)線程都通過 Object 類的引用調(diào)用了 wait() 方法造成阻塞,最后一個(gè)線程調(diào)用 notifyAll() 則喚醒了所有調(diào)用 wait() 方法的線程,如以下代碼:

public static void main(String[] args) {
        Object object = new Object();//實(shí)例化一個(gè)Object類的對象
        Thread thread1 = new Thread(()->{
            synchronized (object) {
                System.out.println("thread1-開始");
                try {
                    object.wait();//thread1中調(diào)用wait方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread1-結(jié)束");
            }
        });//創(chuàng)建thread1線程
        thread1.start();//啟動thread1線程
        Thread thread2 = new Thread(()->{
            synchronized(object) {
                System.out.println("thread2-開始");
                try {
                    object.wait();//thread2調(diào)用wait方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread2-結(jié)束");
            }
        });//創(chuàng)建thread2線程
        thread2.start();//啟動thread2線程
        Thread thread3 = new Thread(()->{
            synchronized (object) {
                object.notifyAll();//thread3中調(diào)用notifyAll方法
                System.out.println("thread3調(diào)用了notifyAll方法");
            }
        });//創(chuàng)建thread3線程
        thread3.start();//啟動thread3線程
    }

運(yùn)行后打印:

以上代碼,通過 notifyAll() 方法喚醒了所有等待的線程。如果我把 notifyAll() 方法替換為 notify() 方法,此時(shí)就會隨機(jī)喚醒一個(gè)正在等待的線程。如以下打印結(jié)果:

通過上面截圖,我們可以觀察到隨機(jī)喚醒的是 thread1 線程。 

5. wait()和sleep()的區(qū)別

wait 與 sleep 之間的區(qū)別:

  • wait() 方法是 Object 類底下的方法,sleep() 方法是 Thread 類底下的靜態(tài)方法。
  • wait()方法是搭配 synchronized 來使用的,而 sleep() 則不需要。
  • 核心區(qū)別,初心不同,wait() 方法是為了避免線程之前的搶占資源(解決線程之間的順序控制),而 sleep() 方法是為了讓線程休眠特定的時(shí)間。
  • wait() 方法有一個(gè)帶參數(shù)的寫法是用來體現(xiàn)超時(shí)的提醒(避免死等),因此用起來就感覺和 sleep() 方法一樣。

案例:

有兩線程,main 線程與 thread 線程,main 線程內(nèi)包含 thread 線程,main 線程內(nèi)有“Hello main”語句, thread 線程內(nèi)有“Hello thread”語句。

在 main 線程內(nèi)創(chuàng)建一個(gè) thread 線程,并且在 thread 線程內(nèi)使用 Object 類對象調(diào)用帶參的 wait() 方法,并設(shè)置參數(shù) 為2000。

在main 方法內(nèi)使用 Object 類對象調(diào)用 notify() 喚醒 thread 線程。使得輸出 main 線程內(nèi)語句后停頓兩秒輸出 thread 線程內(nèi)語句。

有以下代碼:

public static void main(String[] args) {
        Object object = new Object();//實(shí)例化一個(gè)Object類對象
        Thread thread = new Thread(()->{
            synchronized (object) {
                try {
                    object.wait(2000);//thread調(diào)用了帶參wait方法,停頓了兩秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Hello thread");
            }
        });//創(chuàng)建thread線程
        thread.start();//啟動thread線程
        synchronized (object) {
            object.notify();//main方法內(nèi)調(diào)用notify方法
        }
        System.out.println("Hello main");
    }

運(yùn)行后打印:

輸出“Hello main”語句后停頓了兩秒,輸出“Hello thread”線程。

重點(diǎn): 

  • wait、notify、notifyAll都是 Object 類的方法
  • wait、notify、notifyAll 必須搭配 synchronized 關(guān)鍵字來使用
  • 不帶參數(shù)的 wait 方法會造成死等、帶參數(shù)的 wait 方法則不會
  • wait 方法的初心就是為了線程處于等待、阻塞狀態(tài)
  • notify 方法的初心就是為了喚醒同一對象調(diào)用 wait 方法的隨機(jī)一個(gè)線程
  • notifyAll 方法的初心就是為了喚醒同一對象調(diào)用 wait 方法的所有線程

到此這篇關(guān)于Java多線程中的wait與notify方法詳解的文章就介紹到這了,更多相關(guān)Java的wait與notify內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論