在java中,線程間的通信可以使用wait、notify、notifyAll來進行控制。從名字就可以看出來這3個方法都是跟多線程相關(guān)的,但是可能讓你感到吃驚的是:這3個方法并不是Thread類或者是Runnable接口的方法,而是Object類的3個本地方法。
其實要理解這一點也并不難,調(diào)用一個Object的wait與notify/notifyAll的時候,必須保證調(diào)用代碼對該Object是同步的,也就是說必須在作用等同于synchronized(obj){......}的內(nèi)部才能夠去調(diào)用obj的wait與notify/notifyAll三個方法,否則就會報錯:
java.lang.IllegalMonitorStateException:current thread not owner
也就是說,在調(diào)用這3個方法的時候,當(dāng)前線程必須獲得這個對象的鎖,那么這3個方法就是和對象鎖相關(guān)的,所以是屬于Object的方法而不是Thread,因為不是每個對象都是Thread。所以我們在理解wait、notify、notifyAll之前,先要了解以下對象鎖。
多個線程都持有同一個對象的時候,如果都要進入synchronized(obj){......}的內(nèi)部,就必須拿到這個對象的鎖,synchronized的機制保證了同一時間最多只能有1個線程拿到了對象的鎖,如下圖:

下面我們來看一下這3個方法的作用:
wait:線程自動釋放其占有的對象鎖,并等待notify
notify:喚醒一個正在wait當(dāng)前對象鎖的線程,并讓它拿到對象鎖
notifyAll:喚醒所有正在wait前對象鎖的線程
notify和notifyAll的最主要的區(qū)別是:notify只是喚醒一個正在wait當(dāng)前對象鎖的線程,而notifyAll喚醒所有。值得注意的是:notify是本地方法,具體喚醒哪一個線程由虛擬機控制;notifyAll后并不是所有的線程都能馬上往下執(zhí)行,它們只是跳出了wait狀態(tài),接下來它們還會是競爭對象鎖。
下面通過一個常用生產(chǎn)者、消費者的例子來說明。
消息實體類:
package com.podongfeng;
/**
* Title: Message.class<br>
* Description: 消息實體<br>
* Create DateTime: 2016年04月17日 下午1:27 <br>
*
* @author podongfeng
*/
public class Message {
}
生產(chǎn)者:
package com.podongfeng;
import java.util.ArrayList;
import java.util.List;
/**
* Title: Producer.class<br>
* Description: 消息生產(chǎn)者<br>
* Create DateTime: 2016年04月17日 下午1:28 <br>
*
* @author podongfeng
*/
public class Producer extends Thread {
List<Message> msgList = new ArrayList<>();
@Override public void run() {
try {
while (true) {
Thread.sleep(3000);
Message msg = new Message();
synchronized(msgList) {
msgList.add(msg);
msgList.notify(); //這里只能是notify而不能是notifyAll,否則remove(0)會報java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public Message waitMsg() {
synchronized(msgList) {
if(msgList.size() == 0) {
try {
msgList.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
return msgList.remove(0);
}
}
}
消費者:
package com.podongfeng;
/**
* Title: Consumer.class<br>
* Description: 消息消費者<br>
* Create DateTime: 2016年04月17日 下午1:28 <br>
*
* @author podongfeng
*/
public class Consumer extends Thread {
private Producer producer;
public Consumer(String name, Producer producer) {
super(name);
this.producer = producer;
}
@Override public void run() {
while (true) {
Message msg = producer.waitMsg();
System.out.println("Consumer " + getName() + " get a msg");
}
}
public static void main(String[] args) {
Producer p = new Producer();
p.start();
new Consumer("Consumer1", p).start();
new Consumer("Consumer2", p).start();
new Consumer("Consumer3", p).start();
}
}
消費者線程調(diào)用waitMsg去獲取一個消息實體,如果msgList為空,則線程進入wait狀態(tài);生產(chǎn)這線程每隔3秒鐘生產(chǎn)出體格msg實體并放入msgList列表,完成后,調(diào)用notify喚醒一個消費者線程去消費。
最后再次提醒注意:
wait、notify、notifyAll并不是Thread類或者是Runnable接口的方法,而是Object類的3個本地方法。
在調(diào)用這3個方法的時候,當(dāng)前線程必須獲得這個對象的鎖