Java線程間協(xié)作wait、notify和notifyAll詳解
概要描述
在 Java 中可以用 wait、notify 和 notifyAll 來(lái)實(shí)現(xiàn)線程間的通信。盡管關(guān)于wait和notify的概念很基礎(chǔ),它們也都是Object類的函數(shù),但用它們來(lái)寫代碼卻并不簡(jiǎn)單。
wait, notify, notifyAll 都是基類Object的方法,而不屬于Thread,這讓習(xí)慣了調(diào)用Thread.sleep()使線程阻塞的同學(xué)感到奇怪。不過(guò)這樣設(shè)計(jì)是有道理的,因?yàn)檫@些方法操作的鎖(monitor)也是對(duì)象的一部分??梢姡csleep不同,通過(guò)調(diào)用共享對(duì)象的wait方法使當(dāng)前線程等待;通過(guò)調(diào)用對(duì)象的notify, notifyAll 方法喚醒該對(duì)象上的等待線程。
先來(lái)看官方文檔,Java doc對(duì)wait方法的描述:
public final void wait() throws InterruptedException
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. In other words, this method behaves exactly as if it simply performs the call wait(0).
The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown. Java doc對(duì)notify的描述:
public final void notify()
Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.
The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.
This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways:
By executing a synchronized instance method of that object.
By executing the body of a synchronized statement that synchronizes on the object.
For objects of type Class, by executing a synchronized static method of that class.
Only one thread at a time can own an object's monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of this object's monitor.生產(chǎn)者-消費(fèi)者示例:
/**
*
*/
public class Producer extends Thread {
private volatile Queue<Integer> queue;
private int maxSize;
public Producer(Queue<Integer> queue, int maxSize) {
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true) {
//wait,notify方法必須在同步代碼中運(yùn)行
synchronized (queue) {
//條件一定在循環(huán)中判斷,以防死鎖
while (queue.size() == maxSize) {
try { System.out.println(Thread.currentThread().getName() + " wait.");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int i = new Random().nextInt();
System.out.println(Thread.currentThread().getName() + " produce: " + i);
queue.add(i);
queue.notifyAll();
}
}
}
}消費(fèi)者:
/**
*
*/
public class Consumer extends Thread {
private volatile Queue<Integer> queue;
private int maxSize;
public Consumer(Queue<Integer> queue, int maxSize) {
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
try {
System.out.println(Thread.currentThread().getName() + " wait.");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " consume: " + queue.remove());
queue.notifyAll();
}
}
}
}main方法:
/**
*
*/
public class WaitNotifyMain {
private volatile static Queue<Integer> queue = new LinkedList<>();
/**
* @param args
*/
public static void main(String[] args) {
Producer producer = new Producer(queue, 10);
producer.setName("Producer");//設(shè)置線程名稱
Consumer consumer = new Consumer(queue, 10);
consumer.setName("Consumer");
producer.start();
consumer.start();
}
}打印輸出:
Producer produce: -2017386252
Producer produce: 1186339917
Producer produce: -674757828
Producer produce: 605757848
Producer produce: -539314860
Producer produce: 490590935
Producer produce: -845855520
Producer produce: -1459720588
Producer produce: 1274488529
Producer produce: 55225134
Producer wait.
Consumer consume: -2017386252
Consumer consume: 1186339917
Consumer consume: -674757828
Consumer consume: 605757848
Consumer consume: -539314860
Consumer consume: 490590935
Consumer consume: -845855520
Consumer consume: -1459720588
Consumer consume: 1274488529
Consumer consume: 55225134
Consumer wait.
Producer produce: 673397316
Producer produce: -1176693368
Producer produce: -1707265532
Producer produce: -1614197913
Producer produce: 306171031
Producer produce: -1646438955
Producer produce: 1141572321
Producer produce: 1235215288
Producer produce: -692805724
Producer produce: -2131184778
Producer wait.
Consumer consume: 673397316
Consumer consume: -1176693368
總結(jié)
wait, notify, notifyAll 是共享對(duì)象上的調(diào)用,而不是線程對(duì)象的調(diào)用。
wait, notify, notifyAll一定要在共享對(duì)象同步方法或同步代碼塊中執(zhí)行,否則會(huì)在運(yùn)行時(shí)拋出IllegalMonitorStateException的異常。因?yàn)閣ait, notify, notifyAll包含了對(duì)共享對(duì)象鎖的操作,所以之前一定要先synchronized獲取對(duì)象鎖。
在共享對(duì)象上調(diào)用wait()時(shí),當(dāng)前線程進(jìn)入等待狀態(tài), 并釋放剛獲取的對(duì)象鎖(Thread.sleep()是不釋放鎖的),讓出CPU, 此時(shí),其他線程可以調(diào)用共享對(duì)象的同步方法或代碼塊。
喚醒線程在共享對(duì)象上執(zhí)行notify會(huì)隨機(jī)喚醒該對(duì)象的其中之一等待線程;喚醒線程在共享對(duì)象上執(zhí)行notifyAll會(huì)喚醒該對(duì)象上的所有等待線程;這里要著重注意兩點(diǎn):
A,喚醒線程執(zhí)行完共享對(duì)象的notify或notifyAll方法后,仍然要執(zhí)行完synchronized修飾的同步代碼塊中后面的代碼才能釋放對(duì)象鎖,因此,通常notify后面盡量減少執(zhí)行代碼,讓對(duì)象鎖盡快釋放。
B, 喚醒是指線程ready的狀態(tài),尚未運(yùn)行,共享對(duì)象的鎖被喚醒線程釋放后,ready狀態(tài)的線程跟普通線程一樣需要競(jìng)爭(zhēng)共享對(duì)象的鎖,執(zhí)行同步代碼塊中wait()后面的代碼。
永遠(yuǎn)在循環(huán)(loop)里調(diào)用 wait 和 notify, notifyAll,不是在 If 語(yǔ)句,避免死鎖情況發(fā)生。
基于以上認(rèn)知,下面這個(gè)是使用wait和notify函數(shù)的規(guī)范代碼模板:
// The standard idiom for calling the wait method in Java
synchronized (sharedObject) {
while (condition) {
sharedObject.wait();
// (Releases lock, and reacquires on wakeup)
}
// do action based upon condition e.g. take or put into queue
}在while循環(huán)里使用wait的目的,是在線程被喚醒的前后都持續(xù)檢查條件是否被滿足。如果條件并未改變,wait被調(diào)用之前notify的喚醒通知就來(lái)了,那么這個(gè)線程并不能保證被喚醒,有可能會(huì)導(dǎo)致死鎖問(wèn)題。
到此這篇關(guān)于Java線程間協(xié)作wait、notify和notifyAll詳解的文章就介紹到這了,更多相關(guān)Java線程間協(xié)作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA實(shí)現(xiàn)Base64編碼的三種方式
本文主要介紹了JAVA實(shí)現(xiàn)Base64編碼的三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
一文詳解Spring事務(wù)的實(shí)現(xiàn)與本質(zhì)
這篇文章主要介紹了Spring中事務(wù)的兩種實(shí)現(xiàn)方式:聲明式事務(wù)、編程式事務(wù)以及他們的本質(zhì)。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04
解決spring項(xiàng)目找不到Aspect依賴注解的問(wèn)題
這篇文章主要介紹了解決spring項(xiàng)目找不到Aspect依賴注解的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
springboot系統(tǒng)首頁(yè)自動(dòng)跳轉(zhuǎn)拼接到index的實(shí)現(xiàn)
文章介紹了通過(guò)訪問(wèn)http://localhost:8091時(shí),服務(wù)器會(huì)動(dòng)態(tài)跳轉(zhuǎn)到系統(tǒng)的歡迎頁(yè)面,實(shí)現(xiàn)原理是在程序啟動(dòng)時(shí)自動(dòng)加載默認(rèn)的請(qǐng)求路徑,并動(dòng)態(tài)拼接前綴和后綴,最終指向./WEB-INF/views/index.jsp,作者分享了這一經(jīng)驗(yàn),并希望得到大家的支持2025-11-11
使用springboot防止反編譯proguard+xjar
介紹了三種代碼混淆和加密工具的使用方法:ProGuard、Xjar和ClassFinal,ProGuard用于混淆Java字節(jié)碼,Xjar提供對(duì)JAR包內(nèi)資源的加密和動(dòng)態(tài)解密,而ClassFinal則支持直接加密JAR包或WAR包,通過(guò)預(yù)研和實(shí)際操作2024-11-11
Springboot集成任務(wù)調(diào)度實(shí)現(xiàn)過(guò)程
這篇文章主要介紹了Springboot集成任務(wù)調(diào)度實(shí)現(xiàn)過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04

