一豫尽、使用用例
public class ThreadTest {
static final Object obj = new Object();
private static boolean flag = false;
public static void main(String[] args) throws Exception {
Thread consume = new Thread(new Consume(), "Consume");
Thread produce = new Thread(new Produce(), "Produce");
consume.start();
Thread.sleep(1000);
produce.start();
try {
produce.join();//強(qiáng)制生產(chǎn)者先退出
consume.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生產(chǎn)者線程
static class Produce implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("進(jìn)入生產(chǎn)者線程");
System.out.println("生產(chǎn)");
try {
TimeUnit.MILLISECONDS.sleep(2000); //模擬生產(chǎn)過程
flag = true;
obj.notify(); //通知消費(fèi)者
System.out.println("通知消費(fèi)者?");
TimeUnit.MILLISECONDS.sleep(1000); //模擬其他耗時操作
System.out.println("退出生產(chǎn)者線程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消費(fèi)者線程
static class Consume implements Runnable {
@Override
public void run() {
System.out.println("進(jìn)入消費(fèi)者線程");
System.out.println("wait flag 1:" + flag);
synchronized (obj) {
while (!flag) { //判斷條件是否滿足,若不滿足則等待
try {
System.out.println("還沒生產(chǎn)卢鹦,進(jìn)入等待");
obj.wait();
System.out.println("結(jié)束等待");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("wait flag 2:" + flag);
System.out.println("消費(fèi)");
System.out.println("退出消費(fèi)者線程");
}
}
}
運(yùn)行結(jié)果
進(jìn)入消費(fèi)者線程
wait flag 1:false
還沒生產(chǎn),進(jìn)入等待
進(jìn)入生產(chǎn)者線程
生產(chǎn)
通知消費(fèi)者劝堪?
退出生產(chǎn)者線程
結(jié)束等待
wait flag 2:true
消費(fèi)
退出消費(fèi)者線程
二冀自、原理
問題1:為什么wait/nofity需要配合synchronized使用
問題2:明明消費(fèi)者線程獲得了鎖,并沒走完synchronized方法秒啦,生產(chǎn)者是如何進(jìn)入到synchronized的熬粗?
問題3:生產(chǎn)者是如何通知消費(fèi)者的?
問題4:生產(chǎn)者在調(diào)用notify的時候余境,消費(fèi)者為何并沒有被喚醒驻呐?
synchronized:代碼塊通過javap生成的字節(jié)碼中包含 monitorenter 和 monitorexit 指令,執(zhí)行monitorenter指令可以獲取對象的monitor
查看Object.wait源碼芳来,上面有句注釋
This method should only be called by a thread that is the owner of this object's monitor
@問題1
Object.wait實(shí)際調(diào)用的是wait(0)含末;wait(0)上面的注釋寫到
This method causes the current thread (call it <var>T</var>) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object
意思就是wait方法會導(dǎo)致把當(dāng)前線程放到wait set隊列,并釋放所有monitor對象即舌,等待被喚醒
@問題2
ObjectMonitor:
每個線程都有ObjectMonitor對象佣盒,ObjectMonitor對象維護(hù)了free和used的objectMonitor對象列表,如果當(dāng)前free列表為空顽聂,將向全局global list請求分配ObjectMonitor
WaitSet :處于wait狀態(tài)的線程肥惭,會被加入到wait set;
EntryList:處于等待鎖block狀態(tài)的線程紊搪,會被加入到entry set务豺;
ObjectWaiter:
ObjectWaiter對象是雙向鏈表結(jié)構(gòu),保存了_thread(當(dāng)前線程)以及當(dāng)前的狀態(tài)TState等數(shù)據(jù)嗦明, 每個等待鎖的線程都會被封裝成ObjectWaiter對象笼沥。
wait方法實(shí)現(xiàn)
lock.wait()方法最終通過ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);實(shí)現(xiàn):
1、將當(dāng)前線程封裝成ObjectWaiter對象;
2奔浅、通過ObjectMonitor::AddWaiter方法將ObjectWaiter添加到_WaitSet列表中馆纳;
3、通過ObjectMonitor::exit方法釋放當(dāng)前的ObjectMonitor對象汹桦,這樣其它競爭線程就可以獲取該ObjectMonitor對象鲁驶。
4、最終底層的park方法會掛起線程舞骆;
@問題3
notify:
lock.notify()方法最終通過ObjectMonitor的void notify(TRAPS)實(shí)現(xiàn):
1钥弯、如果當(dāng)前_WaitSet為空,即沒有正在等待的線程督禽,則直接返回脆霎;
2、通過ObjectMonitor::DequeueWaiter方法狈惫,獲取_WaitSet列表中的第一個ObjectWaiter節(jié)點(diǎn)睛蛛,實(shí)現(xiàn)也很簡單。
這里需要注意的是胧谈,在jdk的notify方法注釋是隨機(jī)喚醒一個線程忆肾,其實(shí)是第一個ObjectWaiter節(jié)點(diǎn)
3、根據(jù)不同的策略菱肖,將取出來的ObjectWaiter節(jié)點(diǎn)客冈,加入到_EntryList或則通過Atomic::cmpxchg_ptr指令進(jìn)行自旋操作cxq,具體代碼實(shí)現(xiàn)有點(diǎn)長稳强,這里就不貼了郊酒,有興趣的同學(xué)可以看objectMonitor::notify方法;
@問題4
notifyAll:
lock.notifyAll()方法最終通過ObjectMonitor的void notifyAll(TRAPS)實(shí)現(xiàn):
通過for循環(huán)取出_WaitSet的ObjectWaiter節(jié)點(diǎn)键袱,并根據(jù)不同策略,加入到_EntryList或則進(jìn)行自旋操作摹闽。
從JVM的方法實(shí)現(xiàn)中蹄咖,可以發(fā)現(xiàn):notify和notifyAll并不會釋放所占有的ObjectMonitor對象,其實(shí)真正釋放ObjectMonitor對象的時間點(diǎn)是在執(zhí)行monitorexit指令付鹿,一旦釋放ObjectMonitor對象了澜汤,entry set中ObjectWaiter節(jié)點(diǎn)所保存的線程就可以開始競爭ObjectMonitor對象進(jìn)行加鎖操作了。