摘要:主線程啟動這個線程后,將該變量置為,觀察線程是否打印出那行,如果存在可見性問題,主線程修改值為,線程看的值應該還是。
說到并發安全時,我們常提及可見性的問題,通俗點講就是線程1看不到線程2寫入變量v的值(更專業的解釋以及是什么導致可見性問題,又該如何解決,見擴展閱讀),但一直偏于理論,實際中有沒有因可見性而導致問題的例子呢?回答是肯定的,接下來我們一起來看幾個例子。
這個例子很簡單,新建的線程里有一個普通變量stop,用來表示是否結束循環里的自增操作。主線程啟動這個線程后,將該變量置為true,觀察線程是否打印出finish loop那行,如果存在可見性問題,主線程修改stop值為true,線程v看stop的值應該還是false。
class VisibilityThread extends Thread { private boolean stop; public void run() { int i = 0; System.out.println("start loop."); while(!getStop()) { i++; } System.out.println("finish loop,i=" + i); } public void stopIt() { stop = true; } public boolean getStop(){ return stop; } } public class VisibilityTest { public static void main(String[] args) throws Exception { VisibilityThread v = new VisibilityThread(); v.start(); Thread.sleep(1000);//停頓1秒等待新啟線程執行 System.out.println("即將置stop值為true"); v.stopIt(); Thread.sleep(1000); System.out.println("finish main"); System.out.println("main中通過getStop獲取的stop值:" + v.getStop()); } }
我們先來執行一遍(操作系統:XP,下同。JDK:見圖示):
執行結果如上圖,有人該問了,線程v最終停下來了,這不是表示它看到stop值為true了嗎?是的,確實如此。但讓我們再看一個這個程序的執行結果。
這一次,我們發現程序一直未能結束,表示線程v看到stop的值是false,但是主線程打印出的值卻是true。
對比兩次的執行方式,我們發現后一次加上了-server選項。顯示version的時候也由Client VM變成了Server VM。那么Client VM與Server VM有什么區別在哪里?簡單地講,Client VM啟動時做了一般優化,耗時少,啟動快,但程序執行的也相對也較慢;Server VM啟動的時候做了更多優化,耗時多,啟動慢,但程序執行快。如果在運行java命令的時候沒有指定具體模式的時候,會有一個默認值,這個默認值隨硬件和操作系統的不同而不同,這里有張JDK 1.6在各平臺默認VM模式的圖。
我們再來看個例子,這個例子源于hotspot VM的一個bug:
public class InterruptedVisibilityTest { public void think() { System.out.println("新線程正在執行"); while (true) { if (checkInterruptedStatus()) break; } System.out.println("新線程退出循環"); } private boolean checkInterruptedStatus() { return Thread.currentThread().isInterrupted(); } public static void main(String[] args) throws Exception { final InterruptedVisibilityTest test = new InterruptedVisibilityTest(); Thread thinkerThread = new Thread("Thinker") { public void run() { test.think(); } }; thinkerThread.start(); Thread.sleep(1000);//等待新線程執行 System.out.println("馬上中斷thinkerThread"); thinkerThread.interrupt(); System.out.println("已經中斷thinkerThread"); thinkerThread.join(3000); if (thinkerThread.isAlive()) { System.err.println("thinkerThread未能在中斷后3s停止"); System.err.println("JMV bug"); System.err.println("主線程中檢測thinkerThread的中斷狀態:" + thinkerThread.isInterrupted()); } } }
這個例子也很簡單,thinkerThread一直檢查中斷狀態,主線程在啟動thinkerThread之后的某個時刻調用interrupt中斷thinkerThread。在《The Java Language Specification Java SE 7 Edition》§17.4.4中我們能夠看到,如果線程1調用線程2的interrupt方法,那么所有線程(包括線程2)都能通過Thread.isInterrupted方法來檢測到這個中斷狀態。這里直接用hotspot VM的-server模式執行一下,結果如下圖:
thinkerThread沒能退出循環,沒看到主線程所置的中斷狀態。
后面這個例子是hotpost VM的一個bug導致的,在最新的hotspot中應該已經被修復了(筆者未測試最新版)。其它VM如IBM J9,JRockit,harmony等并沒有發現這樣的bug。說這是bug,是因為JLS中規定了main發出的中斷必須對thinkerThread可見。但是,如第一個例子,則不是bug,因為JLS是允許這種行為的。當在第一個例子的循環中的i++后面加上一句Thread.yield()調用(該調用在規范中并沒有特殊內存語義),這我使用的這個版本的VM上,就看不到可見性問題了。這也說明,JVM的優化是無法預知的,允許可見性的地方不一定就真會出現或一直出現。
JLS允許未充分同步的代碼出現可見性問題,但是某個實際的JVM完全可以實現的比JLS上規定的更強,比如不允許可見性問題出現,那么,在這樣的JVM上就展現不出這樣的問題了。第一個例子這里只是運行在hotpost下,也許在其它JVM下同樣采用最優化的方式執行,可能并不會出現這里的問題。
在我們編碼的時候,也許并不知道代碼會跑在什么樣的系統上,不知道會采用什么樣的JVM,為了使得寫出的代碼更健壯,我們只能按照規范所規定的最低保證去編碼,要避免這類問題,只有保證代碼充分同步,避免數據爭用,而不應該依賴于某個具體JVM實現。即使是具體的某款JVM,不同的版本間也可能存在著差異。
最后,這樣的例子啟發我們,測試代碼的時候應盡可能啟用各JVM的最佳優化模式。
擴展閱讀:至此,我們已經了解到實際中多線程運行真的會出現這樣的場景。為什么會出現可見性問題?有什么解決方案?下面鏈接中的內容為我們提供了專業的解答。
http://ifeve.com/volatile/
http://ifeve.com/java-memory-model-0/
http://ifeve.com/java-concurrency-constructs/
CSDN上有人發的一個真實案例
via ifeve
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/64061.html
摘要:有可能一個線程中的動作相對于另一個線程出現亂序。當實際輸出取決于線程交錯的結果時,這種情況被稱為競爭條件。這里的問題在于代碼塊不是原子性的,而且實例的變化對別的線程不可見。這種不能同時在多個線程上執行的部分被稱為關鍵部分。 為什么要額外寫一篇文章來研究volatile呢?是因為這可能是并發中最令人困惑以及最被誤解的結構。我看過不少解釋volatile的博客,但是大多數要么不完整,要么難...
摘要:關鍵字總結有個關鍵字,它們是接下來對其中常用的幾個關鍵字進行概括。而通過關鍵字,并不能解決非原子操作的線程安全性。為了在一個特定對象的一個域上關閉,可以在這個域前加上關鍵字。是語言的關鍵字,用來表示一個域不是該對象串行化的一部分。 java 關鍵字總結 Java有50個關鍵字,它們是: abstract do implements private ...
摘要:今天開始整理學習多線程的知識,談談最重要的兩個關鍵字和。但是這樣一個過程比較慢,在使用多線程的時候就會出現問題。有序性有序性是指多線程執行結果的正確性。這種機制在多線程中會出現問題,因此可以通過來禁止重排。 今天開始整理學習多線程的知識,談談最重要的兩個關鍵字:volatile和synchronized。 一、三個特性 1、原子性 所謂原子性操作就是指這些操作是不可中斷的,要么執行過程...
摘要:首當其沖的便是接口中的每個聲明必須是即便不指定也是,并且不能設置為非,詳細規則可參考可見性部分介紹。函數式接口有著不同的場景,并被認為是對編程語言的一種強大的擴展。抽象類與中的接口有些類似,與中支持默認方法的接口更為相像。 原文鏈接:http://www.javacodegeeks.com/2015/09/how-to-design-classes-and-interfaces.htm...
閱讀 2101·2021-11-18 10:02
閱讀 2850·2021-09-04 16:41
閱讀 1142·2019-08-30 15:55
閱讀 1405·2019-08-29 17:27
閱讀 1070·2019-08-29 17:12
閱讀 2535·2019-08-29 15:38
閱讀 2855·2019-08-29 13:02
閱讀 2831·2019-08-29 12:29