1. 程式人生 > >synchronized實現可見性對比volatile

synchronized實現可見性對比volatile

最近花時間研究了下,如有不正確的地方,歡迎大家批評指正,謝謝。

首先先介紹一下JMM(JAVA記憶體模型),上圖:

java記憶體模型的工作原理如上圖所示,一些被定義的變數都存放在主記憶體中,當一個執行緒想要修改一個變數的值時,那麼這個變數會從主記憶體中拷貝到執行緒的工作記憶體(CPU快取)中。之後執行緒對變數值做了更改,又會重新拷貝回主記憶體中。大家通過描述也可以看出來這些操作是分步執行的,這樣就無法保證可見性和原子性。對於這種情況java也給出了很多解決辦法,今天跟大家分享一下我對synchronized以及volatile的理解。

大家知道synchronized是通過加互斥鎖來實現原子性的,JMM關於synchronized的兩條規定:

  1. 執行緒解鎖前,必須把共享變數的最新之重新整理到主記憶體中
  2. 執行緒加鎖前,將清空工作記憶體中共享變數的值,從而使用共享變數時需要從主記憶體中重新讀取最新的值(注意:加鎖與解鎖需要時同一把鎖)

我來簡單描敘一下執行緒執行互斥程式碼的過程:

  1. 獲得互斥鎖
  2. 清空工作記憶體
  3. 從主記憶體拷貝變數的最新副本到工作記憶體
  4. 執行程式碼
  5. 將更改後的共享變數的值重新整理到主記憶體
  6. 釋放互斥鎖

synchronized從而實現類原子性,也具備記憶體可見性。

這裡多說一下Lock,其實原理跟synchronized類似,但是比synchronized更加靈活,我們會在下一篇部落格中詳細探討synchronized的缺陷以及Lock的基本用法。

volatile是如何實現記憶體可見性的呢?

深入來說:是通過加入記憶體屏障和禁止重排序優化來實現的。(重排序指單執行緒中在保證執行結果不變的前提下java虛擬機器為了提升處理速度可能會將指令重排,達到最合理化)

  • 對volatile變數執行寫操作時,會在寫操作後加入一條store屏障指令
  1. 改變執行緒工作記憶體中的volatile變數副本的值
  2. 將改變後的副本的值從工作記憶體重新整理到主記憶體
  • 對volatile變數執行讀操作時,會在讀操作前加入一條load屏障指令
  1. 從主記憶體中讀取volatile變數的最新值到執行緒的工作記憶體中
  2. 從工作記憶體中讀取volatile變數的副本

簡單來說:volatile變數在每次被執行緒訪問時,都強迫從sy主記憶體中重讀變數的值,而當該變數發生變化時,又會強迫執行緒將最新的值重新整理到主記憶體。這樣在任何時刻,不同的執行緒總能看到該變數的最新值。從而保證了變數的記憶體可見性。

synchronized和volatile的比較

  1. volatile不需要加鎖,比synchronized更加輕量級,不會阻塞執行緒
  2. 從記憶體可見性講,volatile讀相當於加鎖,volatile寫相當於解鎖
  3. synchronized既能保證可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性