Java併發編程-開篇:volatile與synchronized

關於多線程的問題,一直是面試過程中最令筆者頭痛的問題。因爲項目開發過程中,很少會遇到多線程的開發任務(可能是本人還比較low)。所以打算認真來學習一下多線程、高併發相關的知識。以《Java併發編程的藝術》這本書作爲主要學習資料來看看。並且記錄在自己的博當中,作爲讀書筆記。

Java中所使用的併發機制依賴於JVM的實現和CPU指令。

volatile關鍵字的應用:

Java語言規範中對volatile的定義如下:Java編程語言允許線程訪問共享變量,爲確保共享變量能被準確和一致地更新,線程應該確保通過排他鎖單獨獲得這個變量。Java語言提供了volatile,在某些情況下比鎖要更加方便。如果一個字段被申明成volatile,Java線程內存模型確保所有線程看到這個變量的值是一致的。

Java的併發機制是由JVM和CPU指令來保障的。那麼volatile變量修飾的共享變量再被多個線程操作的時候會有什麼不同呢?就是編譯形成的字節碼轉換爲彙編指令之後,votaile變量的操作命令是加Lcok前綴的。Lock前綴的指令在多核處理器下會發生兩件事情:

    1)當前處理器緩存行的數據回寫到系統內存(工作內存回寫到主內存);

    2)這個回寫的操作會使其他CPU裏緩存了該地址的數據無效(那麼再用到的時候就需要從主內存更新到工作內存中)。

至於JVM和CPU的底層實現原理,那就不做考究了,如果面試官還追究這樣的問題,我覺得可能他是刁難人的。

緊接着來看synchronized的實現原理以及應用:

synchronized實現同步的基礎:Java中的每一個對象都可以作爲鎖。具體表現形式爲以下3種形式:

  • 對於普通方法同步,鎖是當前實例對象;
  • 對於靜態同步方法,鎖是當前類的Class對象;
  • 對於同步方法塊,鎖是synchronized括號裏配置的對象。

JVM規範中可以看到synchronized在JVM裏的實現原理,JVM基於進入和退出Monitor對象(每個對象都有一個監視器鎖)來實現方法同步和代碼塊同步,兩者的實現方式略有不同。代碼塊是使用monitorenter和monitorexit指令實現的,而方法同步是使用另一種方式實現的(反編譯之後,可以看到常量池中多了ACC_SYNCHRONIZED標示符,那麼如果有這個標識符,那麼就需要去獲取Monitor了),細節在JVM規範裏並沒有詳細說明。但是,方法的同步也可以使用這兩個命令來實現。都是基於Monitor的。

synchronized用的鎖是存在於Java對象頭(Mark  Word(存儲對象的HashCode、分代年齡和鎖標記位))裏的,運行期間,Mark Word裏存儲的數據會跟隨着鎖標誌位的變化而變化。鎖的級別有不同,分別爲:無鎖狀態、偏向鎖、輕量級鎖、重量級鎖。

優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要消額外的消耗,和執行非同步方法相比僅存在納秒級的差距 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗 適用於只有一個線程訪問同步塊場景
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應速度 如果始終得不到鎖競爭的線程,使用自旋會消耗CPU 追求響應時間;
同步塊執行速度非常快
重量級鎖 線程競爭不使用自旋,不會消耗CPU 線程阻塞,響應時間緩慢 追去吞度量;
同步塊執行速度較長

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章