標籤:java解惑 對象鎖
java解惑,77--亂鎖之妖
下面的這段程式類比了一個小車間。程式首先啟動了一個工人線程,該線程在停止時間到來之前會一直工作(至少是假裝在工作),然後程式安排了一個定時器任務(timer task)用來類比一個惡毒的老闆,他會試圖阻止停止時間的到來。最後,主線程作為一個善良的老闆會告訴工人停止時間到了,並且等待工人停止工作。那麼這個程式會列印什麼呢?
<span style="font-size:18px;">public class Worker extends Thread{ private long count = 0; private volatile boolean quittingTime = false; public void run(){ while(!quittingTime){ pretendToWork(); } System.out.println("Beer is good"); } private void pretendToWork(){ try{ Thread.sleep(300); System.out.println("product:"+(count++)); }catch(InterruptedException ex){ } } //It's quitting time ,wait for worker-Called by good boss synchronized void quit() throws InterruptedException{ quittingTime = true; join(); } //Recsind quiting time-Called by evil boss synchronized void keepWorking(){ quittingTime = true; } public static synchronized void main(String[] args) throws InterruptedException{ final Worker worker = new Worker(); worker.start(); Timer t = new Timer(true); t.schedule(new TimerTask(){ public void run(){ worker.keepWorking(); } },500); Thread.sleep(400); worker.quit(); }}</span>
按照書上的解釋:
- 300 ms:工人線程去檢查易變的quittingTime 域,看看停止時間是否已經到了。這個時候並沒有到停止時間,所 以工人線程會回去繼續“工作”。
- 400ms:作為善良的老闆的主線程會去調用工人線程的quit方法。主線程會獲得工人線程執行個體上的鎖(因為quit是一個同步化的方法),將quittingTime的值設為true,並且調用工人線程上的join方法。這個對join方法的調用並不會馬上返回,而是會等待工人線程執行完畢。
- 500m:作為惡毒的老闆定時器任務開始執行。它將試圖調用工人線程的keepWorking方法,但是這個調用將會被阻塞,因為keepWorking是一個同步化的方法,而主線程當時正在執行工人線程上的另一個同步化方法(quit方法)。
- 600ms:工人線程會再次檢查停止時間是否已經到來。由於quittingTime域是易變的,那麼工人線程肯定會看到新的值true,所以它會列印 Beer is good 並結束運行。這會讓主線程對join方法的調用執行返回,隨後主線程也結束了運行。而定時器線程是背景,所以它也會隨之結束運行,整個程式也就結束了。
所以,我們會認為程式將運行不到1秒鐘,列印 Beer is good ,然後正常的結束。但是當你嘗試運行這個程式的時候,你會發現它沒有列印任何東西,而是一直處於掛起狀態(沒有結束)。我們的分析哪裡出錯了呢?
其實,並沒有什麼可以保證上述幾個交叉的事件會按照上面的時間軸發生。無論是Timer類還是Thread.sleep方法,都不能保證具有即時(real-time)性。這就是說,由於這裡計時的粒度太粗,所以上述幾個事件很有可能會在時間軸上互有重疊地交替發生。100毫秒對於電腦來說是一段很長的時間。此外,這個程式被重複地掛起;看起來好像有什麼其他的東西在工作著,事實上,確實是有這種東西。
我們的分析存在著一個基本的錯誤。在500ms時,當作為惡毒老闆的定時器任務運行時,根據時間軸的顯示,它對keepWorking方法的調用會被阻塞,因為keepWorking是一個同步化的方法並且主線程正在同一個對象上執行著同步化方法quit(在Thread.join中等待著)。這些都是對的,keepWorking確實是一個同步化的方法,並且主線程確實正在同一個對象上執行著同步化的quit方法。即使如此,定時器線程仍然可以獲得這個對象上的鎖,並且執行keepWorking方法。這是如何發生的呢?
問題的答案涉及到了Thread.join的實現。這部分內容在關於該方法的文檔中(JDK文檔)是找不到的,至少在迄今為止發布的文檔中如此,也包括5.0版。在內部,Thread.join方法在表示正在被串連(join)的那個Thread執行個體上調用Object.wait方法。這樣就在等待期間釋放了該對象上的鎖。在我們的程式中,這就使得作為惡毒老闆的定時器線程能夠堂而皇之的將quittingTime重新設定成false,儘管此時主線程正在執行同步化的quit方法。這樣的結果是,工人線程永遠不會看到停止時間的到來,它會永遠運行下去。作為善良的老闆的主線程也就永遠不會從join方法中返回了。
使這個程式產生了預料之外的行為的根本原因就是WorkerThread類的作者使用了執行個體上的鎖來確保quit方法和keepWorking方法的互斥,但是這種用法與超類(Thread)內部對該鎖的用法發生了衝突。
但是我多次運行後,均能得到正常結果。。。。。
希望有人解惑,或許是因為我的程式有問題吧
再來說說編寫程式中遇到的小問題吧!
//public class Seventyseventh{// static class Worker extends Thread{//需要寫上static,否則的話,在main裡定義work會出錯。原因:無法從靜態上下文中引用非靜態 變數// private long count = 0;// private volatile boolean quittingTime = false;//體會volatile有時並不能真正的實現鎖機制// public void run(){// while(!quittingTime){// pretendToWork();// }// System.out.println("Beer is good");// }// private void pretendToWork(){// try{// Thread.sleep(300);// System.out.println("product:"+(count++));// }catch(InterruptedException ex){// // }// }// //It's quitting time ,wait for worker-Called by good boss// synchronized void quit() throws InterruptedException{// quittingTime = true;// join();//等待線程結束,也就是執行完run方法// }// //Recsind quiting time-Called by evil boss// synchronized void keepWorking(){// quittingTime = true;// }// }// public static synchronized void main(String[] args) throws InterruptedException{// final Worker worker = new Worker();//必須是final類型,否則的話,在new TimeTask裡調用失敗,原因是從內部類中訪問本地變數worker; 需要被聲明為最終類型// worker.start();// Timer t = new Timer(true);// t.schedule(new TimerTask(){// public void run(){// worker.keepWorking();// }// },500);// Thread.sleep(400);// worker.quit();// }//}
Java解惑 -- 對象鎖的錯亂