在java中羊异,線程間的通信可以使用wait、notify捉撮、notifyAll來進(jìn)行控制怕品。從名字就可以看出來這3個(gè)方法都是跟多線程相關(guān)的,但是可能讓你感到吃驚的是:這3個(gè)方法并不是Thread類或者是Runnable接口的方法巾遭,而是Object類的3個(gè)本地方法肉康。
其實(shí)要理解這一點(diǎn)也并不難,調(diào)用一個(gè)Object的wait與notify/notifyAll的時(shí)候灼舍,必須保證調(diào)用代碼對該Object是同步的吼和,也就是說必須在作用等同于synchronized(obj){......}的內(nèi)部才能夠去調(diào)用obj的wait與notify/notifyAll三個(gè)方法,否則就會報(bào)錯(cuò):
java.lang.IllegalMonitorStateException:current thread not owner
也就是說骑素,在調(diào)用這3個(gè)方法的時(shí)候炫乓,當(dāng)前線程必須獲得這個(gè)對象的鎖,那么這3個(gè)方法就是和對象鎖相關(guān)的献丑,所以是屬于Object的方法而不是Thread末捣,因?yàn)椴皇敲總€(gè)對象都是Thread。所以我們在理解wait创橄、notify塔粒、notifyAll之前,先要了解以下對象鎖筐摘。
多個(gè)線程都持有同一個(gè)對象的時(shí)候卒茬,如果都要進(jìn)入synchronized(obj){......}的內(nèi)部船老,就必須拿到這個(gè)對象的鎖,synchronized的機(jī)制保證了同一時(shí)間最多只能有1個(gè)線程拿到了對象的鎖圃酵,如下圖:
下面我們來看一下這3個(gè)方法的作用:
wait:線程自動釋放其占有的對象鎖柳畔,并等待notify
notify:喚醒一個(gè)正在wait當(dāng)前對象鎖的線程,并讓它拿到對象鎖
notifyAll:喚醒所有正在wait前對象鎖的線程
notify和notifyAll的最主要的區(qū)別是:notify只是喚醒一個(gè)正在wait當(dāng)前對象鎖的線程郭赐,而notifyAll喚醒所有薪韩。值得注意的是:notify是本地方法,具體喚醒哪一個(gè)線程由虛擬機(jī)控制捌锭;notifyAll后并不是所有的線程都能馬上往下執(zhí)行俘陷,它們只是跳出了wait狀態(tài),接下來它們還會是競爭對象鎖观谦。
下面通過一個(gè)常用生產(chǎn)者拉盾、消費(fèi)者的例子來說明。
消息實(shí)體類:
package com.podongfeng;
/**
* Title: Message.class<br>
* Description: 消息實(shí)體<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)會報(bào)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);
}
}
}
消費(fèi)者:
package com.podongfeng;
/**
* Title: Consumer.class<br>
* Description: 消息消費(fèi)者<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();
}
}
消費(fèi)者線程調(diào)用waitMsg去獲取一個(gè)消息實(shí)體捉偏,如果msgList為空,則線程進(jìn)入wait狀態(tài)泻红;生產(chǎn)這線程每隔3秒鐘生產(chǎn)出體格msg實(shí)體并放入msgList列表夭禽,完成后,調(diào)用notify喚醒一個(gè)消費(fèi)者線程去消費(fèi)谊路。
最后再次提醒注意:
wait讹躯、notify、notifyAll并不是Thread類或者是Runnable接口的方法缠劝,而是Object類的3個(gè)本地方法潮梯。
在調(diào)用這3個(gè)方法的時(shí)候,當(dāng)前線程必須獲得這個(gè)對象的鎖