說到併發安全時,咱們常說起可見性的問題,通俗點講就是線程1看不到線程2寫入變量v的值(更專業的解釋以及是什麼致使可見性問題,又該如何解決,見擴展閱讀),但一直偏於理論,實際中有沒有因可見性而致使問題的例子呢?回答是確定的,接下來咱們一塊兒來看幾個例子。html
這個例子很簡單,新建的線程裏有一個普通變量stop,用來表示是否結束循環裏的自增操做。主線程啓動這個線程後,將該變量置爲true,觀察線程是否打印出finish loop那行,若是存在可見性問題,主線程修改stop值爲true,線程v看stop的值應該仍是false。java
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:見圖示):segmentfault
執行結果如上圖,有人該問了,線程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:oracle
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模式執行一下,結果以下圖:ide
thinkerThread沒能退出循環,沒看到主線程所置的中斷狀態。oop
後面這個例子是hotpost VM的一個bug致使的,在最新的hotspot中應該已經被修復了(筆者未測試最新版)。其它VM如IBM J9,JRockit,harmony等並無發現這樣的bug。說這是bug,是由於JLS中規定了main發出的中斷必須對thinkerThread可見。可是,如第一個例子,則不是bug,由於JLS是容許這種行爲的。當在第一個例子的循環中的i++後面加上一句Thread.yield()調用(該調用在規範中並沒有特殊內存語義),這我使用的這個版本的VM上,就看不到可見性問題了。這也說明,JVM的優化是沒法預知的,容許可見性的地方不必定就真會出現或一直出現。post
JLS容許未充分同步的代碼出現可見性問題,可是某個實際的JVM徹底能夠實現的比JLS上規定的更強,好比不容許可見性問題出現,那麼,在這樣的JVM上就展示不出這樣的問題了。第一個例子這裏只是運行在hotpost下,也許在其它JVM下一樣採用最優化的方式執行,可能並不會出現這裏的問題。
在咱們編碼的時候,也許並不知道代碼會跑在什麼樣的系統上,不知道會採用什麼樣的JVM,爲了使得寫出的代碼更健壯,咱們只能按照規範所規定的最低保證去編碼,要避免這類問題,只有保證代碼充分同步,避免數據爭用,而不該該依賴於某個具體JVM實現。即便是具體的某款JVM,不一樣的版本間也可能存在着差別。
最後,這樣的例子啓發咱們,測試代碼的時候應儘量啓用各JVM的最佳優化模式。
至此,咱們已經瞭解到實際中多線程運行真的會出現這樣的場景。爲何會出現可見性問題?有什麼解決方案?下面連接中的內容爲咱們提供了專業的解答。
via ifeve