京東面試題:說說synchronized和volatile的區別
theme: orange highlight: dracula
今天來介紹一下Java
面試中最常會被面試官提到的問題,也是Java
多執行緒中經常被問到的問題:synchronized和volatile的區別,希望能夠幫助到Java
相關方面的求職者。
Java記憶體模型(JMM)
提到這兩個有關於執行緒的關鍵字,那麼我們不得不提到Java
的記憶體模型了(JMM),下面我們先看一下 Java記憶體模型 在處理多執行緒方面的工作原理圖。
Java記憶體模型(java Memory Model) 描述了Java
程式中各種變數(執行緒共享變數)的訪問規則,以及在JVM
中將變數儲存到記憶體和從記憶體中讀取出變數這樣的底層細節。
首先介紹兩個概念
可見性:一個執行緒對共享變數值的修改,能夠及時地被其他執行緒看到。
共享變數:如果一個變數在多個執行緒的工作記憶體中都存在副本,那麼這個變數就是這幾個執行緒的共享變數。
共享變數可見性實現的原理
執行緒1對共享變數的修改要想被執行緒2及時看到,必須要經過如下兩個步驟:
- 把工作記憶體1中更新過的共享變數重新整理到主記憶體中
- 將主記憶體中最新的共享變數的值更新到工作記憶體2中
下圖為一個共享變數實現可見性原理的一個示例:
其中,執行緒對共享變數的操作,遵循以下兩條規則:
- 執行緒對共享變數的所有操作都必須在自己的工作記憶體中進行,不能直接從主記憶體中讀寫
- 不同執行緒之間無法直接訪問其他執行緒工作記憶體中的變數,執行緒間變數值的傳遞需要通過主記憶體來完成
可見性
要實現共享變數的可見性,必須保證兩點:
- 執行緒修改後的共享變數值能夠及時從工作記憶體重新整理到主記憶體中
- 其他執行緒能夠及時把共享變數的最新值從主記憶體更新到自己的工作記憶體中
可見性的實現方式 - synchronized - volatile
synchronoized實現可見性
- 原子性(同步)
- 可見性
JMM關於synchronized的兩條規定
- 執行緒解鎖前,必須把共享變數的最新值重新整理到主記憶體中
- 執行緒加鎖時,將清空工作記憶體中共享變數的值,從而使用共享變數時,需要從主記憶體中重新讀取最新的值(注意:加鎖與解鎖需要是同一把鎖)
注意:執行緒解鎖前對共享變數的修改在下次加鎖時對其他執行緒可見
執行緒執行互斥程式碼的過程
- 獲得互斥鎖
- 清空工作記憶體
- 從主記憶體拷貝變數的最新副本到工作的記憶體
- 執行程式碼
- 將更改後的共享變數的值重新整理到主記憶體
- 釋放互斥鎖
重排序
程式碼書寫的順序與實際執行的順序不同,指令重排序是編譯器或處理器為了提高程式效能而做的優化
- 編譯器優化的重排序(編譯器優化)
- 指令級並行重排序(處理器優化)
- 記憶體系統的重排序(處理器優化)
volatile實現可見性
volatile
關鍵字能夠保證volatile
變數的可見性- 不能保證
volatile
變數複合操作的原子性
volatile如何實現記憶體可見性
深入來說:通過加入記憶體屏障和禁止重排序優化來實現的
- 對
volatile
變數執行寫操作時,會在寫操作後加入一條store
屏障指令 - 對
volatile
變數執行讀操作時,會在讀操作前加入一條load
屏障指令
執行緒寫volatile變數的過程
- 改變執行緒工作記憶體中
volatile
變數副本的值 - 將改變後的副本的值從工作記憶體重新整理到主記憶體
執行緒讀volatile
變數的過程
- 從主記憶體中讀取
volatile
變數的最新值到執行緒的工作記憶體中 - 從工作記憶體中讀取
volatile
變數的副本
下圖是volatile
不能實現原子性的示例:
``` volatile不能保證volatile變數複合操作的原子性:
private int number = 0;
number++; // 不是原子操作
加入synchronized,變為原子操作: synchronized(this) { number++; } ```
實現原子操作解決方案
保證number
自增操作的原子性:
- 使用synchronized
關鍵字
- 使用ReentrantLock
- 使用AtomicInteger
可重入鎖案例
// Reentranlock
public int increase(){
lock.lock();
try {
number++;
} finally {
lock.onlock();
}
}
volatile適用場合
要在多執行緒中安全的使用volatile
變數,必須同時滿足:
- 對變數的寫入操作不依賴其當前值
- 不滿足:number++、count=count*5 等
- 滿足:
boolean
變數、記錄溫度變化的變數等
- 該變數沒有包含在具有其他變數的不變式中
總結
synchronized和volatile的區別
volatile
不需要加鎖,比synchronized
更輕量級,不會阻塞執行緒;- 從記憶體可見性角度,
volatile
讀相當於加鎖,volatile
寫相當於解鎖; synchronized
既能夠保證可見性,又能保證原子性,而volatile
只能保證可見性,無法保證原子性。