Java線程中synchronized和volatile關鍵字的區(qū)別詳解
前言
提到synchronized和volatile這兩個有關線程的關鍵字,那我們先來說一下Java的內存模型(JMM)。
Java內存模型(java Memory Model)描述了Java程序中各種變量(線程共享變量)的訪問規(guī)則,以及在JVM中將變量存儲到內存和從內存中讀取出變量這樣的底層細節(jié)。
首先了解兩個概念:
- 可見性: 一個線程對共享變量值的修改,能夠及時地被其他線程看到。
- 共享變量: 如果一個變量在多個線程的工作內存中都存在副本,那么這個變量就是這幾個線程的共享變量。
共享變量可見性實現的原理
線程1對共享變量的修改要想被線程2及時看到,必須要經過以下兩個步驟:
- 在工作內存1中更新過的共享變量刷新到主內存中;
- 將主內存中最新的共享變量的值更新到在工作內存2中
其中線程對共享變量的操作,遵循以下兩個規(guī)則:
- 線程對共享變量的所有操作都必須在自己的工作內存中進行,不能直接從主內存中讀寫;
- 不同線程之間無法直接訪問其他線程工作內存的變量,線程間變量值的傳遞需要通過主內存來完成。
可見性
要實現共享變量的可見性,必須保證兩點:
- 線程修改后的共享變量能夠及時從工作內存刷新到主內存中
- 其他線程能夠及時把共享變量的最新值從主內存更新到自己的工作內存中
可見性的實現方式:
- synchronized
- volatile
synchronized實現可見性
- 線程解鎖前,必須把共享變量的最新值刷新到主內存中;
- 線程加鎖時,將清空工作內存中共享變量的值,從而使用共享變量時,需要從主內尋中重新讀取最新的值(注意:加鎖和解鎖試一把鎖)
注意:線程解鎖前對共享變量的修改在下次加鎖時對其他線程可見
線程執(zhí)行互斥代碼的過程:
- 獲得互斥鎖;
- 清空工作內存;
- 從主內存拷貝變量的最新副本到工作的內存;
- 執(zhí)行代碼;
- 將更改后的共享變量的值刷新到主內存;
- 釋放互斥鎖.
重排序:
代碼書寫的順序與實際執(zhí)行的順序不同,指令重排序是編譯器或處理器為了提高程序性能而做的優(yōu)化
- 編譯器優(yōu)化的重排序(編譯器優(yōu)化)
- 指令級并行重排序(處理器優(yōu)化)
- 內存系統(tǒng)的重排序(處理器優(yōu)化)
volatile實現可見性
volatile通過加入內存屏障和禁止重排序優(yōu)化來實現的
- 對volatile變量執(zhí)行寫操作時,會在寫操作后加入一條store屏障指令
- 對volatile變量執(zhí)行讀操作時,會在讀操作前加入一條load屏障指令
線程寫volatile變量的過程:
- 改變線程工作內存中volatile變量副本的值
- 將改變后的副本的值從工作內存刷新到主內存
線程讀volatile變量的過程:
- 從主內存中讀取volatile變量的最新值到線程的工作內存中;
- 從工作內存中讀取volatile變量的副本;
volatile不能保證volatile變量復合操作的原子性:
不是原子操作;
private int number = 0; number ++ ;
加入synchronized,變?yōu)樵硬僮?/p>
synchronized(this){ number++; }
變?yōu)関olatile變量,無法保障原子性
private volatile int number = 0;
解決方案:
保證number自增操作的原子性:
- 使用synchronized關鍵字
- 使用ReentrantLock(java.util.concurrent.locks包下)
- 使用AtomicInterger(java.util.concurrent.atomic包下)
//Reentranlock public int increase(){ lock.lock(); try{ number++; }finally{ lock.unlock(); } return number; }
volatile適用場合
要在多線程中安全的使用volatile變量,必須同時滿足:
- 對變量的寫入操作不依賴其當前值;
- 不滿足:number++、count=count *5等
- 滿足:boolean變量、記錄溫度變化的變量等
- 該變量沒有包含在具有其他變量的不變式中
- 不滿足: low<up
總結
synchronized和volatile的區(qū)別
volatile不需要加鎖,比synchronized更輕量級,不會阻塞線程;從內存可見性角度,volatile讀相當于加鎖,volatile寫相當于解鎖;synchronzied既能夠保障可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性。
到此這篇關于Java線程中synchronized和volatile關鍵字的區(qū)別詳解的文章就介紹到這了,更多相關synchronized和volatile關鍵字內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!