欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線(xiàn)程中的wait與notify方法詳解

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

 前言

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

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

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

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

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

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

因此得使用 wait() 方法與 notify() 方法來(lái)解決該問(wèn)題。

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

把三個(gè)線(xiàn)程比做人,把一臺(tái) ATM 機(jī)比作鎖(synchronized)。

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

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

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

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

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

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

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

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

注意:wait,notify、notifyAll都是 Object 類(lèi)中的方法。

1. wait()方法

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

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

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

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

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

    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 來(lái)使用,脫離了 synchronized 的前提下 使用 wait 就會(huì)出現(xiàn)報(bào)錯(cuò)。

2. notify()方法

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

notify 方法作用:

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

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

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

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

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

因此,有以下代碼:

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

運(yùn)行后打印:

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

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

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

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

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

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

4. notifyAll()方法

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

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

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

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

運(yùn)行后打印:

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

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

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

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

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

案例:

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

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

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

有以下代碼:

public static void main(String[] args) {
        Object object = new Object();//實(shí)例化一個(gè)Object類(lèi)對(duì)象
        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線(xiàn)程
        thread.start();//啟動(dòng)thread線(xiàn)程
        synchronized (object) {
            object.notify();//main方法內(nèi)調(diào)用notify方法
        }
        System.out.println("Hello main");
    }

運(yùn)行后打印:

輸出“Hello main”語(yǔ)句后停頓了兩秒,輸出“Hello thread”線(xiàn)程。

重點(diǎn): 

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

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

相關(guān)文章

最新評(píng)論