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

分析java并發(fā)中的wait notify notifyAll

 更新時(shí)間:2021年06月11日 14:20:39   作者:SharpCJ  
一個(gè)線程修改一個(gè)對(duì)象的值,而另一個(gè)線程則感知到了變化,然后進(jìn)行相應(yīng)的操作,這就是wait()、notify()和notifyAll()方法的本質(zhì)。本文將詳細(xì)來(lái)介紹它們概念實(shí)現(xiàn)以及區(qū)別

一、前言

java 面試是否有被問(wèn)到過(guò),sleepwait 方法的區(qū)別,關(guān)于這個(gè)問(wèn)題其實(shí)不用多說(shuō),大多數(shù)人都能回答出最主要的兩點(diǎn)區(qū)別:

  • sleep 是線程的方法, wait / notify / notifyAll 是 Object 類的方法;
  • sleep 不會(huì)釋放當(dāng)前線程持有的鎖,到時(shí)間后程序會(huì)繼續(xù)執(zhí)行,wait 會(huì)釋放線程持有的鎖并掛起,直到通過(guò) notify 或者 notifyAll 重新獲得鎖。

另外還有一些參數(shù)、異常等區(qū)別,不細(xì)說(shuō)了。本文重點(diǎn)記錄一下 wait / notify / notifyAll 的相關(guān)知識(shí)。

二、常見(jiàn)的同步場(chǎng)景

開(kāi)發(fā)中常常遇到這樣的場(chǎng)景:

一個(gè)線程執(zhí)行過(guò)程中,需要開(kāi)啟另外一個(gè)子線程去做某個(gè)耗時(shí)的操作(通過(guò)休眠3秒模擬),并且**等待**子線程返回結(jié)果,主線程再根據(jù)返回的結(jié)果繼續(xù)往下執(zhí)行。

這里注意我上面加*兩個(gè)字“等待”。如果不需要等待,單純只是對(duì)子線程的結(jié)果做處理,我們大可注冊(cè)回調(diào)方法解決問(wèn)題,此文不再贅述接口回調(diào)。

此處場(chǎng)景就是主線程停下來(lái)等待子線程執(zhí)行完畢后,主線程再繼續(xù)執(zhí)行。針對(duì)該場(chǎng)景下面給出實(shí)現(xiàn):

2.1、設(shè)置一個(gè)判斷的標(biāo)志位

volatile boolean flag = false;

public void test(){
    //...

    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(3000);
            System.out.println("--- 休眠 3 秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            flag = true;
        }
    });
    t1.start();

    while(!flag){

    }
    System.out.println("--- work thread run");
}

上面的代碼,執(zhí)行結(jié)果:

強(qiáng)調(diào)一點(diǎn),聲明標(biāo)志位的時(shí)候,一定注意 volatile 關(guān)鍵字不能忘,如果不加該關(guān)鍵字修飾,程序可能進(jìn)入死循環(huán)。這是同步中的可見(jiàn)性問(wèn)題,在 《java 并發(fā)——內(nèi)置鎖》 中有記錄。

顯然,這個(gè)實(shí)現(xiàn)方案并不好,本來(lái)主線程什么也不用做,卻一直在競(jìng)爭(zhēng)資源,做空循環(huán),性能上不好,所以并不推薦。

2.2、線程的 join 方法

public void test(){
    //...

    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(3000);
            System.out.println("--- 休眠 3 秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    t1.start();

    try {
        t1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("--- work thread continue");
}

上面的代碼,執(zhí)行結(jié)果同上。利用 Thread 類的 join 方法實(shí)現(xiàn)了同步,達(dá)到了效果,但是 join 方法不能一定保證效果,在不同的 cpu 上,可能呈現(xiàn)出意想不到的結(jié)果,所以盡量不要用上述方法。

2.3、使用閉鎖 CountDownLatch

不清楚閉鎖的新同學(xué)可以了解下java并發(fā)中的線程。

public void test(){
    //...

    final CountDownLatch countDownLatch = new CountDownLatch(1);

    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(3000);
            System.out.println("--- 休眠 3 秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            countDownLatch.countDown();
        }
    });

    t1.start();

    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("--- work thread run");
}

上面的代碼,執(zhí)行結(jié)果同上。同樣可以實(shí)現(xiàn)上述效果,執(zhí)行結(jié)果和上面一樣。該方法推薦使用。

2.4、利用 wait / notify 優(yōu)化標(biāo)志位方法

為了方便對(duì)比,首先給 2.1 中的循環(huán)方法增加一些打印。修改后的代碼如下:

volatile boolean flag = false;

public void test() {
    //...
    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(3000);
            System.out.println("--- 休眠 3 秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            flag = true;
        }
    });
    t1.start();

    while (!flag) {
        try {
            System.out.println("---while-loop---");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("--- work thread run");
}

執(zhí)行結(jié)果如下:

事實(shí)證明,while 循環(huán)確實(shí)一直在執(zhí)行。

為了使該線程再不需要執(zhí)行的時(shí)候不搶占資源,我們可以利用 wait 方法將其掛起,在需要它執(zhí)行的時(shí)候,再利用 notify 方法將其喚醒。這樣達(dá)到優(yōu)化的目的,優(yōu)化后的代碼如下:

volatile boolean flag = false;

public void test() {
    //...
    final Object obj = new Object();
    Thread t1 = new Thread(() -> {
        synchronized (obj) {
            try {
                Thread.sleep(3000);
                System.out.println("--- 休眠 3 秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                flag = true;
            }
            obj.notify();
        }
    });
    t1.start();

    synchronized (obj) {
        while (!flag) {
            try {
                System.out.println("---while-loop---");
                Thread.sleep(500);
                obj.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    System.out.println("--- work thread run");

}

執(zhí)行結(jié)果:

結(jié)果證明,優(yōu)化后的程序,循環(huán)只執(zhí)行了一次。

三、理解 wait / notify / notifyAll

在Java中,每個(gè)對(duì)象都有兩個(gè)池,鎖(monitor)池和等待池

3.1、鎖池

鎖池:假設(shè)線程A已經(jīng)擁有了某個(gè)對(duì)象的鎖,而其它的線程想要調(diào)用這個(gè)對(duì)象的某個(gè)synchronized方法(或者synchronized塊),由于這些線程在進(jìn)入對(duì)象的synchronized方法之前必須先獲得該對(duì)象的鎖的擁有權(quán),但是該對(duì)象的鎖目前正被線程A擁有,所以這些線程就進(jìn)入了該對(duì)象的鎖池中。

3.2、等待池

等待池:假設(shè)一個(gè)線程A調(diào)用了某個(gè)對(duì)象的wait()方法,線程A就會(huì)釋放該對(duì)象的鎖(因?yàn)閣ait()方法必須出現(xiàn)在synchronized中,這樣自然在執(zhí)行wait()方法之前線程A就已經(jīng)擁有了該對(duì)象的鎖),同時(shí)線程A就進(jìn)入到了該對(duì)象的等待池中。如果另外的一個(gè)線程調(diào)用了相同對(duì)象的notifyAll()方法,那么處于該對(duì)象的等待池中的線程就會(huì)全部進(jìn)入該對(duì)象的鎖池中,準(zhǔn)備爭(zhēng)奪鎖的擁有權(quán)。如果另外的一個(gè)線程調(diào)用了相同對(duì)象的notify()方法,那么僅僅有一個(gè)處于該對(duì)象的等待池中的線程(隨機(jī))會(huì)進(jìn)入該對(duì)象的鎖池.

3.3、notify 和 notifyAll 的區(qū)別

3.3.1、wait()

public final void wait() throws InterruptedException,IllegalMonitorStateException
該方法用來(lái)將當(dāng)前線程置入休眠狀態(tài),直到接到通知或被中斷為止。在調(diào)用 wait()之前,線程必須要獲得該對(duì)象的對(duì)象級(jí)別鎖,即只能在同步方法或同步塊中調(diào)用 wait()方法。進(jìn)入 wait()方法后,當(dāng)前線程釋放鎖。在從 wait()返回前,線程與其他線程競(jìng)爭(zhēng)重新獲得鎖。如果調(diào)用 wait()時(shí),沒(méi)有持有適當(dāng)?shù)逆i,則拋出 IllegalMonitorStateException,它是 RuntimeException 的一個(gè)子類,因此,不需要 try-catch 結(jié)

3.3.2、notify()

public final native void notify() throws IllegalMonitorStateException
該方法也要在同步方法或同步塊中調(diào)用,即在調(diào)用前,線程也必須要獲得該對(duì)象的對(duì)象級(jí)別鎖,的如果調(diào)用 notify()時(shí)沒(méi)有持有適當(dāng)?shù)逆i,也會(huì)拋出 IllegalMonitorStateException。
該方法用來(lái)通知那些可能等待該對(duì)象的對(duì)象鎖的其他線程。如果有多個(gè)線程等待,則線程規(guī)劃器任意挑選出其中一個(gè) wait()狀態(tài)的線程來(lái)發(fā)出通知,并使它等待獲取該對(duì)象的對(duì)象鎖(notify 后,當(dāng)前線程不會(huì)馬上釋放該對(duì)象鎖,wait 所在的線程并不能馬上獲取該對(duì)象鎖,要等到程序退出 synchronized 代碼塊后,當(dāng)前線程才會(huì)釋放鎖,wait所在的線程也才可以獲取該對(duì)象鎖),但不驚動(dòng)其他同樣在等待被該對(duì)象notify的線程們。當(dāng)?shù)谝粋€(gè)獲得了該對(duì)象鎖的 wait 線程運(yùn)行完畢以后,它會(huì)釋放掉該對(duì)象鎖,此時(shí)如果該對(duì)象沒(méi)有再次使用 notify 語(yǔ)句,則即便該對(duì)象已經(jīng)空閑,其他 wait 狀態(tài)等待的線程由于沒(méi)有得到該對(duì)象的通知,會(huì)繼續(xù)阻塞在 wait 狀態(tài),直到這個(gè)對(duì)象發(fā)出一個(gè) notify 或 notifyAll。這里需要注意:它們等待的是被 notify 或 notifyAll,而不是鎖。這與下面的 notifyAll()方法執(zhí)行后的情況不同。

3.3.3、notifyAll()

public final native void notifyAll() throws IllegalMonitorStateException

該方法與 notify ()方法的工作方式相同,重要的一點(diǎn)差異是:

notifyAll 使所有原來(lái)在該對(duì)象上 wait 的線程統(tǒng)統(tǒng)退出 wait 的狀態(tài)(即全部被喚醒,不再等待 notify 或 notifyAll,但由于此時(shí)還沒(méi)有獲取到該對(duì)象鎖,因此還不能繼續(xù)往下執(zhí)行),變成等待獲取該對(duì)象上的鎖,一旦該對(duì)象鎖被釋放(notifyAll 線程退出調(diào)用了 notifyAll 的 synchronized 代碼塊的時(shí)候),他們就會(huì)去競(jìng)爭(zhēng)。如果其中一個(gè)線程獲得了該對(duì)象鎖,它就會(huì)繼續(xù)往下執(zhí)行,在它退出 synchronized 代碼塊,釋放鎖后,其他的已經(jīng)被喚醒的線程將會(huì)繼續(xù)競(jìng)爭(zhēng)獲取該鎖,一直進(jìn)行下去,直到所有被喚醒的線程都執(zhí)行完畢。

四、生產(chǎn)者與消費(fèi)者模式

生產(chǎn)者與消費(fèi)者問(wèn)題是并發(fā)編程里面的經(jīng)典問(wèn)題。接下來(lái)說(shuō)說(shuō)利用wait()和notify()來(lái)實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者并發(fā)問(wèn)題:
顯然要保證生產(chǎn)者和消費(fèi)者并發(fā)運(yùn)行不出亂,主要要解決:當(dāng)生產(chǎn)者線程的緩存區(qū)為滿的時(shí)候,就應(yīng)該調(diào)用wait()來(lái)停止生產(chǎn)者繼續(xù)生產(chǎn),而當(dāng)生產(chǎn)者滿的緩沖區(qū)被消費(fèi)者消費(fèi)掉一塊時(shí),則應(yīng)該調(diào)用notify()喚醒生產(chǎn)者,通知他可以繼續(xù)生產(chǎn);同樣,對(duì)于消費(fèi)者,當(dāng)消費(fèi)者線程的緩存區(qū)為空的時(shí)候,就應(yīng)該調(diào)用wait()停掉消費(fèi)者線程繼續(xù)消費(fèi),而當(dāng)生產(chǎn)者又生產(chǎn)了一個(gè)時(shí)就應(yīng)該調(diào)用notify()來(lái)喚醒消費(fèi)者線程通知他可以繼續(xù)消費(fèi)了。

下面是一個(gè)簡(jiǎn)單的代碼實(shí)現(xiàn):

package com.sharpcj;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String[] args) {
        Reposity reposity = new Reposity(600);
        ExecutorService threadPool = Executors.newCachedThreadPool();
        for(int i = 0; i < 10; i++){
            threadPool.submit(new Producer(reposity));
        }

        for(int i = 0; i < 10; i++){
            threadPool.submit(new Consumer(reposity));
        }
        threadPool.shutdown();
    }
}


class Reposity {
    private static final int MAX_NUM = 2000;
    private int currentNum;

    private final Object obj = new Object();

    public Reposity(int currentNum) {
        this.currentNum = currentNum;
    }

    public void in(int inNum) {
        synchronized (obj) {
            while (currentNum + inNum > MAX_NUM) {
                try {
                    System.out.println("入貨量 " + inNum + " 線程 " + Thread.currentThread().getId() + "被掛起...");
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            currentNum += inNum;
            System.out.println("線程: " + Thread.currentThread().getId() + ",入貨:inNum = [" + inNum + "], currentNum = [" + currentNum + "]");
            obj.notifyAll();
        }
    }

    public void out(int outNum) {
        synchronized (obj) {
            while (currentNum < outNum) {
                try {
                    System.out.println("出貨量 " + outNum + " 線程 " + Thread.currentThread().getId() + "被掛起...");
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            currentNum -= outNum;
            System.out.println("線程: " + Thread.currentThread().getId() + ",出貨:outNum = [" + outNum + "], currentNum = [" + currentNum + "]");
            obj.notifyAll();
        }
    }
}

class Producer implements Runnable {
    private Reposity reposity;

    public Producer(Reposity reposity) {
        this.reposity = reposity;
    }

    @Override
    public void run() {
        reposity.in(200);
    }
}

class Consumer implements Runnable {
    private Reposity reposity;

    public Consumer(Reposity reposity) {
        this.reposity = reposity;
    }

    @Override
    public void run() {
        reposity.out(200);
    }
}

執(zhí)行結(jié)果:

五、總結(jié)

1.調(diào)用wait方法和notify、notifyAll方法前必須獲得對(duì)象鎖,也就是必須寫(xiě)在synchronized(鎖對(duì)象){......}代碼塊中。

2.當(dāng)線程調(diào)用了wait方法后就釋放了對(duì)象鎖,否則其他線程無(wú)法獲得對(duì)象鎖。

3.當(dāng)調(diào)用 wait() 方法后,線程必須再次獲得對(duì)象鎖后才能繼續(xù)執(zhí)行。

4.如果另外兩個(gè)線程都在 wait,則正在執(zhí)行的線程調(diào)用notify方法只能喚醒一個(gè)正在wait的線程(公平競(jìng)爭(zhēng),由JVM決定)。

5.當(dāng)使用notifyAll方法后,所有wait狀態(tài)的線程都會(huì)被喚醒,但是只有一個(gè)線程能獲得鎖對(duì)象,必須執(zhí)行完while(condition){this.wait();}后才釋放對(duì)象鎖。其余的需要等待該獲得對(duì)象鎖的線程執(zhí)行完釋放對(duì)象鎖后才能繼續(xù)執(zhí)行。

6.當(dāng)某個(gè)線程調(diào)用notifyAll方法后,雖然其他線程被喚醒了,但是該線程依然持有著對(duì)象鎖,必須等該同步代碼塊執(zhí)行完(右大括號(hào)結(jié)束)后才算正式釋放了鎖對(duì)象,另外兩個(gè)線程才有機(jī)會(huì)執(zhí)行。

7.第5點(diǎn)中說(shuō)明, wait 方法的調(diào)用前的條件判斷需放在循環(huán)中,否則可能出現(xiàn)邏輯錯(cuò)誤。另外,根據(jù)程序邏輯合理使用 wait 即 notify 方法,避免如先執(zhí)行 notify ,后執(zhí)行 wait 方法,線程一直掛起之類的錯(cuò)誤。

以上就是分析java并發(fā)中的wait notify notifyAll的詳細(xì)內(nèi)容,更多關(guān)于java并發(fā)wait notify notifyAll的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring使用redis遇到的問(wèn)題及解決方案

    Spring使用redis遇到的問(wèn)題及解決方案

    這篇文章主要介紹了Spring使用redis遇到的問(wèn)題及解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • spring boot補(bǔ)習(xí)系列之幾種scope詳解

    spring boot補(bǔ)習(xí)系列之幾種scope詳解

    這篇文章主要給大家介紹了關(guān)于spring boot補(bǔ)習(xí)系列之幾種scope的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • SpringBoot-RestTemplate如何實(shí)現(xiàn)調(diào)用第三方API

    SpringBoot-RestTemplate如何實(shí)現(xiàn)調(diào)用第三方API

    這篇文章主要介紹了SpringBoot-RestTemplate實(shí)現(xiàn)調(diào)用第三方API的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Java實(shí)現(xiàn)石頭剪刀布游戲

    Java實(shí)現(xiàn)石頭剪刀布游戲

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)石頭剪刀布游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-10-10
  • springboot項(xiàng)目如何引用公共模塊的bean

    springboot項(xiàng)目如何引用公共模塊的bean

    這篇文章主要介紹了springboot項(xiàng)目如何引用公共模塊的bean問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Spring中配置Transaction與不配置的區(qū)別及說(shuō)明

    Spring中配置Transaction與不配置的區(qū)別及說(shuō)明

    這篇文章主要介紹了Spring中配置Transaction與不配置的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • spring源碼閱讀--aop實(shí)現(xiàn)原理講解

    spring源碼閱讀--aop實(shí)現(xiàn)原理講解

    這篇文章主要介紹了spring源碼閱讀--aop實(shí)現(xiàn)原理講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • java字符串拼接與性能分析詳解

    java字符串拼接與性能分析詳解

    在JAVA中拼接兩個(gè)字符串的最簡(jiǎn)便的方式就是使用操作符”+”。如果你用”+”來(lái)連接固定長(zhǎng)度的字符串,可能性能上會(huì)稍受影響,但是如果你是在循環(huán)中來(lái)”+”多個(gè)串的話,性能將指數(shù)倍的下降,下面我們分析一下JAVA字符串拼接的性能
    2014-01-01
  • 利用java+mysql遞歸實(shí)現(xiàn)拼接樹(shù)形JSON列表的方法示例

    利用java+mysql遞歸實(shí)現(xiàn)拼接樹(shù)形JSON列表的方法示例

    這篇文章主要給大家介紹了關(guān)于利用java+mysql遞歸實(shí)現(xiàn)拼接樹(shù)形JSON列表的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來(lái)一起看看吧。
    2017-08-08
  • 如何通過(guò)javacv實(shí)現(xiàn)圖片去水?。ǜ酱a)

    如何通過(guò)javacv實(shí)現(xiàn)圖片去水印(附代碼)

    這篇文章主要介紹了如何通過(guò)javacv實(shí)現(xiàn)圖片去水?。ǜ酱a),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07

最新評(píng)論