JVM原理學習筆記

最近在閱讀 《Inside the JVM》 這本書,結合一些日常工作學習中的感想,隨便寫一些東西,蜻蜓點水,不必有章法。

關於“單例同步”:
    一直有人在問單例對象的併發調用是否需要同步,基本屬於“月經帖”了,答案是現成的滿天下都是,但真正能讓人心裏踏實下來的解釋寥寥無幾。實際上,只要學習了一些JVM的運行原理,解釋這個問題就不難了。
    如果一個類是單例的,比如某些DAO的設計,那麼所有的線程來訪問這個類的實例的時候,它們獲得的都將是同一個對象,這是不言自明的。如果這些線程的當前 操作是“互斥”的,那麼每個線程就必須在取得該實例的訪問資格的時候爲該對象上鎖,以獨享該對象直到當前操作結束,以免在操作中途被其它線程介入而產生不 可預知的結果。問題是,什麼樣的操作是“互斥”的呢?
    簡單地說,互斥操作就是兩個操作企圖對它倆共享的某個資源進行修改,而修改的結果是不可預知的。於是問題就變成了,什麼纔是“共享的資源”?從純粹 java語法的角度這個問題沒法解釋,因爲它遵循的是當前java虛擬機的規範描述。現假設兩個線程正企圖同時訪問一個單例對象的方法,如,

 

一個規範的虛擬機線程在調用method1()的時候是這樣做的:
    1) 把method1()的局部變量,包括參數,壓入當前線程的棧;
    2) 從當前線程棧彈出變量j,並賦予數值3;
    3) 從當前線程棧彈出參數i,與j執行加法運算;
    4) 從當前線程棧中釋放當前方法佔用的棧幀,並把method1()的結果壓入當前線程棧。
需要說明的是,當前線程棧是當前線程獨有的,絕對不會被其它線程訪問到。這樣,只要你在method1()裏面使用的全都是局部變量或參數,那就不需要爲多線程的併發調用發愁,因爲每個線程都有自己的棧幀,各不相干。

    複雜一點,如果method1()是這樣定義的:

這下我們就不得不考慮線程同步問題了,這個方法顯然包含了一個互斥的操作“singleObj.intValue ++;”。 前面說過,方法的參數會被壓入當前線程私有的棧直到方法結束,但這裏要注意的是,singleObj只是一個引用地址而非真正的對象實例,因此,儘管 singleObj這個引用值是被壓入線程私有棧去的,但真正的對象實例卻是在堆裏存放的,棧雖然是線程私有的,堆卻是所有線程共享的,因此 singleObj的成員變量intValue是完全有可能在當前線程執行第二行代碼前被其它線程修改了的。比如說,線程1調用mothod1()的時候 singleObj.intValue的值是1, i的值是2,那麼正確的情況下,method1()的返回值應該是4。但當線程1和線程2幾乎同時調用method1(),線程2恰好在線程1把 intValue變成2之後的一瞬間又執行了一次singleObj.intValue ++,由於singleObj是單例,兩個線程遇到的singleObj是同一個對象,因此這次運算將把intValue變成3。接下來線程1繼續第二行 代碼,結果j的結果變成了i+3 = 2+3 = 5 。 如此一來,線程1調用method1()的返回結果究竟會是 4 還是 5 是無法確定的,只能憑運氣,寄望線程2在線程1從調用method1()到取得返回值之間的這段時間打盹。在絕大多數情況下,這種“憑運氣”的做法是不能 接受的,我們需要向線程1保證,在它調用method1()期間絕不會收到線程2的干擾。做法如下:

 

這個寫法仍然有缺陷,因爲線程2很可能在線程1執行int j = 0 的時候修改singleObj的intValue,所以比較可靠的應該在調用method1()之前鎖住singleObj:

小小總結一下,“一個方法如果涉及對某個共享對象(或堆對象)的寫操作,那麼它必須同步該對象”這個說法在大多數情況下都對,但還有些失之籠統,或許這樣說比較準確些,“如果一個方法對某共享對象的寫操作會造成其它線程返回值的不確定性,則該方法應該同步該對象。”

更正 :本文出現的書名應該是《Inside the JVM》,之前誤寫作《Deep Into JVM》了,感謝fantasybei網友提出來。本書是Java世界的經典著作,有興趣的網友可以用書名在網上找到一大堆資料,其中文譯名是《深入Java虛擬機》

 

 

 

-------------------------------------------------------------------------------------------------------------------------------

 

 

 

最近在閱讀 《Inside the JVM》 這本書,結合一些日常工作學習中的感想,隨便寫一些東西,蜻蜓點水,不必有章法。

    曾經很在意C++和Java之間的優劣比較,有一段時間尤其注意在網上搜索二者比較的文章,並不時參加一些口水戰,比如下面這個帖子:
http://www.diybl.com/course/3_program/c++/cppsl/2008520/117228.html
在論壇裏絕對是個口水飛濺潛力帖。現在想起來很好笑,其實Java跟C++幾乎是不同領域的東西,它們之所以存在是因爲各自領域的需要,比較一下 有助於C++程序員轉移到Java去或相反,但優劣之說只能誤人子弟。帖子的作者顯然不懂虛擬機規範,因此會有所謂“騙局”一說,這裏無意駁斥這個作者早 已無從考證的老文章,僅就虛擬機規範說幾句。

    《Inside the JVM》 一開始就指出,所謂“虛擬機”實際上在不同的語境下有不同的涵義。有時候它指的是虛擬機的“規範”(spec),有時候指的是虛擬機的具體實現(如Sun JDK, BEA JRockit),有時候指的是正在運行着的一個虛擬機的實例(你啓動Tomcat或者JBoss,甚至一個j2se程序,都同時啓動了一個虛擬機實 例)。書中如無特別說明,指的一般都是“規範”。

    虛擬機規範規定了.class文件的格式、類裝載的規則、運行時內存的邏輯區塊、方法調用時棧的動作等等。一旦某個虛擬機的具體實現(如Open JDK)聲稱它實現了Java虛擬機規範,那麼也就是同時聲稱它在運行時的外部行爲跟規範中所描述的是一樣的。對於Java程序員而言,他的“平臺”只有 一個,就是虛擬機規範,只要他的.class編譯完,無論到哪一個虛擬機上,Open JDK也好,JRockit也好,Sun JDK也好,甚至芯片級實現的JVM也好,都應該能夠正常運行。這就是Java跨平臺的真正涵義(當然,實際項目中,100%的跨平臺項目是很少的,比如 有些沒有完全遵守規範寫出的bug,在這個虛擬機上運行的時候或許能糊弄過去,但在另一個虛擬機上卻未必能夠)。帖子作者認爲Java的跨平臺是個“設計 巧妙的騙局”,實在是言重了,我們不能天真地認爲,“跨平臺”就可以沒有平臺,事實上無論怎樣我們總需要至少一個的。此外,即使是“跨平臺”也是相對的, 一個平臺因爲沒有JVM的實現而“跨”不上去,太正常了,世上哪有什麼絕對的事。

發佈了100 篇原創文章 · 獲贊 4 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章