Java的wait(), notify()和notifyAll()使用心得
更新時(shí)間:2013年08月21日 14:46:00 作者:
本篇文章是對(duì)java的 wait(),notify(),notifyAll()進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
wait(),notify()和notifyAll()都是java.lang.Object的方法:
wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notify(): Wakes up a single thread that is waiting on this object's monitor.
notifyAll(): Wakes up all threads that are waiting on this object's monitor.
這三個(gè)方法,都是Java語言提供的實(shí)現(xiàn)線程間阻塞(Blocking)和控制進(jìn)程內(nèi)調(diào)度(inter-process communication)的底層機(jī)制。在解釋如何使用前,先說明一下兩點(diǎn):
1. 正如Java內(nèi)任何對(duì)象都能成為鎖(Lock)一樣,任何對(duì)象也都能成為條件隊(duì)列(Condition queue)。而這個(gè)對(duì)象里的wait(), notify()和notifyAll()則是這個(gè)條件隊(duì)列的固有(intrinsic)的方法。
2. 一個(gè)對(duì)象的固有鎖和它的固有條件隊(duì)列是相關(guān)的,為了調(diào)用對(duì)象X內(nèi)條件隊(duì)列的方法,你必須獲得對(duì)象X的鎖。這是因?yàn)榈却隣顟B(tài)條件的機(jī)制和保證狀態(tài)連續(xù)性的機(jī)制是緊密的結(jié)合在一起的。
(An object's intrinsic lock and its intrinsic condition queue are related: in order to call any of the condition queue methods on object X, you must hold the lock on X. This is because the mechanism for waiting for state-based conditions is necessarily tightly bound to the mechanism fo preserving state consistency)
根據(jù)上述兩點(diǎn),在調(diào)用wait(), notify()或notifyAll()的時(shí)候,必須先獲得鎖,且狀態(tài)變量須由該鎖保護(hù),而固有鎖對(duì)象與固有條件隊(duì)列對(duì)象又是同一個(gè)對(duì)象。也就是說,要在某個(gè)對(duì)象上執(zhí)行wait,notify,先必須鎖定該對(duì)象,而對(duì)應(yīng)的狀態(tài)變量也是由該對(duì)象鎖保護(hù)的。
知道怎么使用后,我們來問下面的問題:
1. 執(zhí)行wait, notify時(shí),不獲得鎖會(huì)如何?
請(qǐng)看代碼:
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
obj.wait();
obj.notifyAll();
}
執(zhí)行以上代碼,會(huì)拋出java.lang.IllegalMonitorStateException的異常。
2. 執(zhí)行wait, notify時(shí),不獲得該對(duì)象的鎖會(huì)如何?
請(qǐng)看代碼:
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Object lock = new Object();
synchronized (lock) {
obj.wait();
obj.notifyAll();
}
}
執(zhí)行代碼,同樣會(huì)拋出java.lang.IllegalMonitorStateException的異常。
3. 為什么在執(zhí)行wait, notify時(shí),必須獲得該對(duì)象的鎖?
這是因?yàn)?,如果沒有鎖,wait和notify有可能會(huì)產(chǎn)生競(jìng)態(tài)條件(Race Condition)??紤]以下生產(chǎn)者和消費(fèi)者的情景:
1.1生產(chǎn)者檢查條件(如緩存滿了)-> 1.2生產(chǎn)者必須等待
2.1消費(fèi)者消費(fèi)了一個(gè)單位的緩存 -> 2.2重新設(shè)置了條件(如緩存沒滿) -> 2.3調(diào)用notifyAll()喚醒生產(chǎn)者
我們希望的順序是: 1.1->1.2->2.1->2.2->2.3
但在多線程情況下,順序有可能是 1.1->2.1->2.2->2.3->1.2。也就是說,在生產(chǎn)者還沒wait之前,消費(fèi)者就已經(jīng)notifyAll了,這樣的話,生產(chǎn)者會(huì)一直等下去。
所以,要解決這個(gè)問題,必須在wait和notifyAll的時(shí)候,獲得該對(duì)象的鎖,以保證同步。
請(qǐng)看以下利用wait,notify實(shí)現(xiàn)的一個(gè)生產(chǎn)者、一個(gè)消費(fèi)者和一個(gè)單位的緩存的簡(jiǎn)單模型:
public class QueueBuffer {
int n;
boolean valueSet = false;
synchronized int get() {
if (!valueSet)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Got: " + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
if (valueSet)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
valueSet = true;
System.out.println("Put: " + n);
notify();
}
}
public class Producer implements Runnable {
private QueueBuffer q;
Producer(QueueBuffer q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) {
q.put(i++);
}
}
}
public class Consumer implements Runnable {
private QueueBuffer q;
Consumer(QueueBuffer q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) {
q.get();
}
}
}
public class Main {
public static void main(String[] args) {
QueueBuffer q = new QueueBuffer();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}
所以,JVM通過在執(zhí)行的時(shí)候拋出IllegalMonitorStateException的異常,來確保wait, notify時(shí),獲得了對(duì)象的鎖,從而消除隱藏的Race Condition。
最后來看看一道題:寫一個(gè)多線程程序,交替輸出1,2,1,2,1,2......
利用wait, notify解決:
public class OutputThread implements Runnable {
private int num;
private Object lock;
public OutputThread(int num, Object lock) {
super();
this.num = num;
this.lock = lock;
}
public void run() {
try {
while(true){
synchronized(lock){
lock.notifyAll();
lock.wait();
System.out.println(num);
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args){
final Object lock = new Object();
Thread thread1 = new Thread(new OutputThread(1,lock));
Thread thread2 = new Thread(new OutputThread(2, lock));
thread1.start();
thread2.start();
}
}
wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notify(): Wakes up a single thread that is waiting on this object's monitor.
notifyAll(): Wakes up all threads that are waiting on this object's monitor.
這三個(gè)方法,都是Java語言提供的實(shí)現(xiàn)線程間阻塞(Blocking)和控制進(jìn)程內(nèi)調(diào)度(inter-process communication)的底層機(jī)制。在解釋如何使用前,先說明一下兩點(diǎn):
1. 正如Java內(nèi)任何對(duì)象都能成為鎖(Lock)一樣,任何對(duì)象也都能成為條件隊(duì)列(Condition queue)。而這個(gè)對(duì)象里的wait(), notify()和notifyAll()則是這個(gè)條件隊(duì)列的固有(intrinsic)的方法。
2. 一個(gè)對(duì)象的固有鎖和它的固有條件隊(duì)列是相關(guān)的,為了調(diào)用對(duì)象X內(nèi)條件隊(duì)列的方法,你必須獲得對(duì)象X的鎖。這是因?yàn)榈却隣顟B(tài)條件的機(jī)制和保證狀態(tài)連續(xù)性的機(jī)制是緊密的結(jié)合在一起的。
(An object's intrinsic lock and its intrinsic condition queue are related: in order to call any of the condition queue methods on object X, you must hold the lock on X. This is because the mechanism for waiting for state-based conditions is necessarily tightly bound to the mechanism fo preserving state consistency)
根據(jù)上述兩點(diǎn),在調(diào)用wait(), notify()或notifyAll()的時(shí)候,必須先獲得鎖,且狀態(tài)變量須由該鎖保護(hù),而固有鎖對(duì)象與固有條件隊(duì)列對(duì)象又是同一個(gè)對(duì)象。也就是說,要在某個(gè)對(duì)象上執(zhí)行wait,notify,先必須鎖定該對(duì)象,而對(duì)應(yīng)的狀態(tài)變量也是由該對(duì)象鎖保護(hù)的。
知道怎么使用后,我們來問下面的問題:
1. 執(zhí)行wait, notify時(shí),不獲得鎖會(huì)如何?
請(qǐng)看代碼:
復(fù)制代碼 代碼如下:
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
obj.wait();
obj.notifyAll();
}
執(zhí)行以上代碼,會(huì)拋出java.lang.IllegalMonitorStateException的異常。
2. 執(zhí)行wait, notify時(shí),不獲得該對(duì)象的鎖會(huì)如何?
請(qǐng)看代碼:
復(fù)制代碼 代碼如下:
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Object lock = new Object();
synchronized (lock) {
obj.wait();
obj.notifyAll();
}
}
執(zhí)行代碼,同樣會(huì)拋出java.lang.IllegalMonitorStateException的異常。
3. 為什么在執(zhí)行wait, notify時(shí),必須獲得該對(duì)象的鎖?
這是因?yàn)?,如果沒有鎖,wait和notify有可能會(huì)產(chǎn)生競(jìng)態(tài)條件(Race Condition)??紤]以下生產(chǎn)者和消費(fèi)者的情景:
1.1生產(chǎn)者檢查條件(如緩存滿了)-> 1.2生產(chǎn)者必須等待
2.1消費(fèi)者消費(fèi)了一個(gè)單位的緩存 -> 2.2重新設(shè)置了條件(如緩存沒滿) -> 2.3調(diào)用notifyAll()喚醒生產(chǎn)者
我們希望的順序是: 1.1->1.2->2.1->2.2->2.3
但在多線程情況下,順序有可能是 1.1->2.1->2.2->2.3->1.2。也就是說,在生產(chǎn)者還沒wait之前,消費(fèi)者就已經(jīng)notifyAll了,這樣的話,生產(chǎn)者會(huì)一直等下去。
所以,要解決這個(gè)問題,必須在wait和notifyAll的時(shí)候,獲得該對(duì)象的鎖,以保證同步。
請(qǐng)看以下利用wait,notify實(shí)現(xiàn)的一個(gè)生產(chǎn)者、一個(gè)消費(fèi)者和一個(gè)單位的緩存的簡(jiǎn)單模型:
復(fù)制代碼 代碼如下:
public class QueueBuffer {
int n;
boolean valueSet = false;
synchronized int get() {
if (!valueSet)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Got: " + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
if (valueSet)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
valueSet = true;
System.out.println("Put: " + n);
notify();
}
}
復(fù)制代碼 代碼如下:
public class Producer implements Runnable {
private QueueBuffer q;
Producer(QueueBuffer q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) {
q.put(i++);
}
}
}
復(fù)制代碼 代碼如下:
public class Consumer implements Runnable {
private QueueBuffer q;
Consumer(QueueBuffer q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) {
q.get();
}
}
}
復(fù)制代碼 代碼如下:
public class Main {
public static void main(String[] args) {
QueueBuffer q = new QueueBuffer();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}
所以,JVM通過在執(zhí)行的時(shí)候拋出IllegalMonitorStateException的異常,來確保wait, notify時(shí),獲得了對(duì)象的鎖,從而消除隱藏的Race Condition。
最后來看看一道題:寫一個(gè)多線程程序,交替輸出1,2,1,2,1,2......
利用wait, notify解決:
復(fù)制代碼 代碼如下:
public class OutputThread implements Runnable {
private int num;
private Object lock;
public OutputThread(int num, Object lock) {
super();
this.num = num;
this.lock = lock;
}
public void run() {
try {
while(true){
synchronized(lock){
lock.notifyAll();
lock.wait();
System.out.println(num);
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args){
final Object lock = new Object();
Thread thread1 = new Thread(new OutputThread(1,lock));
Thread thread2 = new Thread(new OutputThread(2, lock));
thread1.start();
thread2.start();
}
}
您可能感興趣的文章:
- java多線程之wait(),notify(),notifyAll()的詳解分析
- Java 中Object的wait() notify() notifyAll()方法使用
- Java使用wait() notify()方法操作共享資源詳解
- Java多線程通信wait()和notify()代碼實(shí)例
- java wait()/notify() 實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式詳解
- Java多線程wait()和notify()方法詳細(xì)圖解
- java使用wait()和notify()線程間通訊的實(shí)現(xiàn)
- JAVA中wait()和notify()如何使用詳解
相關(guān)文章
Java?C++題解leetcode902最大為N的數(shù)字組合數(shù)位DP
這篇文章主要為大家介紹了Java?C++題解leetcode902最大為N的數(shù)字組合數(shù)位DP,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10淺談Spring事務(wù)傳播行為實(shí)戰(zhàn)
這篇文章主要介紹了淺談Spring事務(wù)傳播行為實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Java類型轉(zhuǎn)換valueOf與parseInt區(qū)別探討解析
這篇文章主要為大家介紹了Java類型轉(zhuǎn)換valueOf與parseInt區(qū)別探討解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09mybatis-plus讀取JSON類型的方法實(shí)現(xiàn)
這篇文章主要介紹了mybatis-plus讀取JSON類型的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09SpringBoot項(xiàng)目整合Redis教程詳解
這篇文章主要介紹了SpringBoot項(xiàng)目整合Redis教程詳解,Redis?是完全開源的,遵守?BSD?協(xié)議,是一個(gè)高性能的?key-value?數(shù)據(jù)庫(kù)。感興趣的小伙伴可以參考閱讀本文2023-03-03