自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java并發(fā)編程:使用Wait和Notify方法的注意事項(xiàng)

開(kāi)發(fā) 前端
save?方法負(fù)責(zé)向緩沖區(qū)添加數(shù)據(jù),然后執(zhí)行notify?方法來(lái)喚醒之前等待的線程。take方法負(fù)責(zé)檢查緩沖區(qū)是否為空。如果為空,線程進(jìn)入等待狀態(tài);如果不為空,線程從緩沖區(qū)中取出數(shù)據(jù)。

在之前的講解線程狀態(tài)的文章中,我們提到了wait和notify方法可以讓線程在運(yùn)行狀態(tài)和等待狀態(tài)之間轉(zhuǎn)換。在這篇文章中,我們將深入探討wait、notify和notifyAll方法在使用中的注意事項(xiàng)。我們主要從三個(gè)問(wèn)題入手:

  • 為什么wait方法必須在synchronized保護(hù)的代碼中使用?
  • 為什么wait方法需要在循環(huán)操作中使用?
  • wait/notify和sleep方法有什么異同?

1. 為什么wait()方法必須在synchronized修飾的代碼中使用?

為了找到這個(gè)問(wèn)題的答案,我們不妨反過(guò)來(lái)思考:如果不要求在synchronized代碼中使用wait方法,會(huì)出現(xiàn)什么問(wèn)題呢?讓我們來(lái)看這段代碼。

public class QueueDemo {
    Queue<String> buffer = new LinkedList<String>();
    public void save(String data) {
        buffer.add(data);
        notify(); // 因?yàn)榭赡苡芯€程在 take() 方法中等待
    }

    public String take() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait();
        }
        return buffer.remove();
    }
}

在這段代碼中,有兩個(gè)方法。save方法負(fù)責(zé)向緩沖區(qū)添加數(shù)據(jù),然后執(zhí)行notify方法來(lái)喚醒之前等待的線程。take方法負(fù)責(zé)檢查緩沖區(qū)是否為空。如果為空,線程進(jìn)入等待狀態(tài);如果不為空,線程從緩沖區(qū)中取出數(shù)據(jù)。

這段代碼沒(méi)有使用synchronized保護(hù),可能會(huì)出現(xiàn)以下情況:

  • 首先,消費(fèi)者線程調(diào)用take方法,并判斷buffer.isEmpty是否返回true。如果返回true,表示緩沖區(qū)為空,線程準(zhǔn)備進(jìn)入等待狀態(tài)。然而,在線程調(diào)用wait方法之前,它被可能已經(jīng)被掛起了,wait方法沒(méi)有執(zhí)行。
  • 此時(shí),生產(chǎn)者線程開(kāi)始運(yùn)行,并執(zhí)行了整個(gè)save方法。它向緩沖區(qū)添加了數(shù)據(jù),并執(zhí)行了notify方法,但notify沒(méi)有效果,因?yàn)橄M(fèi)者線程的wait方法還沒(méi)有執(zhí)行,所以沒(méi)有線程在等待被喚醒。
  • 隨后,之前被掛起的消費(fèi)者線程恢復(fù)執(zhí)行,并調(diào)用了wait方法,進(jìn)入等待狀態(tài)。

出現(xiàn)這個(gè)問(wèn)題的原因是這里的“判斷 - 執(zhí)行”不是原子操作,它在中間被中斷,是線程不安全的。

假設(shè)此時(shí)沒(méi)有更多的生產(chǎn)者進(jìn)行生產(chǎn),消費(fèi)者可能會(huì)陷入無(wú)限等待,因?yàn)樗e(cuò)過(guò)了save方法中的notify喚醒。

你可以模擬一個(gè)生產(chǎn)者線程和一個(gè)消費(fèi)者線程分別調(diào)用這兩個(gè)方法:

public class QueueDemo2 {
    Queue<String> buffer = new LinkedList<>();
    public void save(String data) {
        System.out.println("Produce a data");
        buffer.add(data);
        notify(); // 因?yàn)榭赡苡腥嗽?take() 中等待
    }
    public String take() throws InterruptedException {
        System.out.println("Try to consume a data");
        while (buffer.isEmpty()) {
            wait();
        }
        return buffer.remove();
    }
    public static void main(String[] args) throws InterruptedException {
        QueueDemo2 queueDemo = new QueueDemo2();
        Thread producerThread = new Thread(() -> {
            queueDemo.save("Hello World!");
        });
        Thread consumerThread = new Thread(() -> {
            try {
                System.out.println(queueDemo.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        consumerThread.start();
        producerThread.start();
    }
}

你可以嘗試執(zhí)行這段代碼,看看是否會(huì)出現(xiàn)之前提到的問(wèn)題。

實(shí)際輸出如下:

Try to consume a data
Produce a data
Exception in thread "Thread-0" Exception in thread "Thread-1"
java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at thread.basic.chapter4.QueueDemo2.save(QueueDemo2.java:13)
    at thread.basic.chapter4.QueueDemo2.lambda$main$0(QueueDemo2.java:28)
    at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at thread.basic.chapter4.QueueDemo2.take(QueueDemo2.java:19)
    at thread.basic.chapter4.QueueDemo2.lambda$main$1(QueueDemo2.java:33)

根本沒(méi)有犯錯(cuò)的機(jī)會(huì)。wait方法和notify方法在沒(méi)有synchronized保護(hù)的代碼塊中執(zhí)行時(shí),會(huì)直接拋出java.lang.IllegalMonitorStateException異常。

修改代碼:

public class SyncQueueDemo2 {
    Queue<String> buffer = new LinkedList<>();
    public synchronized void save(String data) {
        System.out.println("Produce a data");
        buffer.add(data);
        notify(); // 因?yàn)榭赡苡腥嗽?take() 中等待
    }
    public synchronized String take() throws InterruptedException {
        System.out.println("Try to consume a data");
        while (buffer.isEmpty()) {
            wait();
        }
        return buffer.remove();
    }
    public static void main(String[] args) throws InterruptedException {
        SyncQueueDemo2 queueDemo = new SyncQueueDemo2();
        Thread producerThread = new Thread(() -> {
            queueDemo.save("Hello World!");
        });
        Thread consumerThread = new Thread(() -> {
            try {
                System.out.println(queueDemo.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        consumerThread.start();
        producerThread.start();
    }
}

再次執(zhí)行代碼,輸出如下:

Produce a data
Try to consume a data
Hello World!

可以看到,生產(chǎn)的"Hello World!"已經(jīng)被成功消費(fèi)并打印到控制臺(tái)。

2. 為什么wait方法需要在循環(huán)操作中使用?

線程調(diào)用wait方法后,可能會(huì)出現(xiàn)虛假喚醒(spurious wakeup)的情況,即線程在沒(méi)有被notify/notifyAll調(diào)用、沒(méi)有被中斷、也沒(méi)有超時(shí)的情況下被喚醒,這是我們不希望發(fā)生的情況。

雖然在真實(shí)環(huán)境中,虛假喚醒的概率非常小,但程序仍然需要在虛假喚醒的情況下保證正確性,因此需要使用while循環(huán)結(jié)構(gòu)。

while (條件不滿足) {
    obj.wait();
}

這樣,即使線程被虛假喚醒,如果條件不滿足,wait會(huì)繼續(xù)執(zhí)行,從而消除虛假喚醒導(dǎo)致的風(fēng)險(xiǎn)。

3.wait/notify和sleep方法有什么異同?

wait方法和sleep方法的相同點(diǎn)如下:

  • 它們都可以阻塞線程。
  • 它們都可以響應(yīng)中斷:如果在等待過(guò)程中收到中斷信號(hào),它們會(huì)響應(yīng)并拋出InterruptedException異常。

它們之間也有很多不同點(diǎn):

  • wait方法必須在synchronized保護(hù)的代碼中使用,而sleep方法沒(méi)有這個(gè)要求。
  • 當(dāng)sleep方法在synchronized代碼中執(zhí)行時(shí),它不會(huì)釋放鎖,而wait方法會(huì)主動(dòng)釋放鎖。
  • sleep方法需要定義一個(gè)時(shí)間,時(shí)間到期后線程會(huì)主動(dòng)恢復(fù)。對(duì)于沒(méi)有參數(shù)的wait方法,它意味著永久等待,直到被中斷或喚醒,不會(huì)主動(dòng)恢復(fù)。
  • wait和notify是Object類的方法,而sleep是Thread類的方法。

好了,這次的內(nèi)容就到這里,下次再見(jiàn)!

責(zé)任編輯:武曉燕 來(lái)源: 程序猿技術(shù)充電站
相關(guān)推薦

2021-07-10 08:37:36

Notify機(jī)制Java

2022-09-23 09:25:04

代碼方法

2009-06-12 09:46:40

Java String

2010-03-15 18:25:27

Java編程語(yǔ)言

2009-09-01 17:25:33

初學(xué)C#編程

2010-11-26 16:27:01

MySQL使用變量

2009-08-27 10:40:56

Java路徑

2023-12-12 09:06:06

2011-06-23 11:15:25

SEO網(wǎng)站優(yōu)化

2015-08-05 09:33:21

Javawaitnotify

2024-02-01 09:39:02

asyncawaitPromise

2010-08-12 09:39:26

FlexaddChil

2011-07-28 17:29:22

HBaseShell

2012-03-12 16:46:22

NoSQL數(shù)據(jù)庫(kù)

2010-01-21 11:30:10

2021-12-20 23:22:46

Java開(kāi)發(fā)升級(jí)

2011-05-26 11:22:04

SEO

2009-06-11 17:52:08

JavaBean

2009-06-25 14:41:06

JavaBean

2011-03-22 08:56:30

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)