Java線程中synchronized和volatile關(guān)鍵字的區(qū)別詳解
前言
提到synchronized和volatile這兩個有關(guān)線程的關(guān)鍵字,那我們先來說一下Java的內(nèi)存模型(JMM)。
Java內(nèi)存模型(java Memory Model)描述了Java程序中各種變量(線程共享變量)的訪問規(guī)則,以及在JVM中將變量存儲到內(nèi)存和從內(nèi)存中讀取出變量這樣的底層細(xì)節(jié)。
首先了解兩個概念:
- 可見性: 一個線程對共享變量值的修改,能夠及時地被其他線程看到。
- 共享變量: 如果一個變量在多個線程的工作內(nèi)存中都存在副本,那么這個變量就是這幾個線程的共享變量。
共享變量可見性實(shí)現(xiàn)的原理
線程1對共享變量的修改要想被線程2及時看到,必須要經(jīng)過以下兩個步驟:
- 在工作內(nèi)存1中更新過的共享變量刷新到主內(nèi)存中;
- 將主內(nèi)存中最新的共享變量的值更新到在工作內(nèi)存2中
其中線程對共享變量的操作,遵循以下兩個規(guī)則:
- 線程對共享變量的所有操作都必須在自己的工作內(nèi)存中進(jìn)行,不能直接從主內(nèi)存中讀寫;
- 不同線程之間無法直接訪問其他線程工作內(nèi)存的變量,線程間變量值的傳遞需要通過主內(nèi)存來完成。
可見性
要實(shí)現(xiàn)共享變量的可見性,必須保證兩點(diǎn):
- 線程修改后的共享變量能夠及時從工作內(nèi)存刷新到主內(nèi)存中
- 其他線程能夠及時把共享變量的最新值從主內(nèi)存更新到自己的工作內(nèi)存中
可見性的實(shí)現(xiàn)方式:
- synchronized
- volatile
synchronized實(shí)現(xiàn)可見性
- 線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存中;
- 線程加鎖時,將清空工作內(nèi)存中共享變量的值,從而使用共享變量時,需要從主內(nèi)尋中重新讀取最新的值(注意:加鎖和解鎖試一把鎖)
注意:線程解鎖前對共享變量的修改在下次加鎖時對其他線程可見
線程執(zhí)行互斥代碼的過程:
- 獲得互斥鎖;
- 清空工作內(nèi)存;
- 從主內(nèi)存拷貝變量的最新副本到工作的內(nèi)存;
- 執(zhí)行代碼;
- 將更改后的共享變量的值刷新到主內(nèi)存;
- 釋放互斥鎖.
重排序:
代碼書寫的順序與實(shí)際執(zhí)行的順序不同,指令重排序是編譯器或處理器為了提高程序性能而做的優(yōu)化
- 編譯器優(yōu)化的重排序(編譯器優(yōu)化)
- 指令級并行重排序(處理器優(yōu)化)
- 內(nèi)存系統(tǒng)的重排序(處理器優(yōu)化)
volatile實(shí)現(xiàn)可見性
volatile通過加入內(nèi)存屏障和禁止重排序優(yōu)化來實(shí)現(xiàn)的
- 對volatile變量執(zhí)行寫操作時,會在寫操作后加入一條store屏障指令
- 對volatile變量執(zhí)行讀操作時,會在讀操作前加入一條load屏障指令
線程寫volatile變量的過程:
- 改變線程工作內(nèi)存中volatile變量副本的值
- 將改變后的副本的值從工作內(nèi)存刷新到主內(nèi)存
線程讀volatile變量的過程:
- 從主內(nèi)存中讀取volatile變量的最新值到線程的工作內(nèi)存中;
- 從工作內(nèi)存中讀取volatile變量的副本;
volatile不能保證volatile變量復(fù)合操作的原子性:
不是原子操作;
private int number = 0; number ++ ;
加入synchronized,變?yōu)樵硬僮?/p>
synchronized(this){ number++; }
變?yōu)関olatile變量,無法保障原子性
private volatile int number = 0;
解決方案:
保證number自增操作的原子性:
- 使用synchronized關(guān)鍵字
- 使用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變量,必須同時滿足:
- 對變量的寫入操作不依賴其當(dāng)前值;
- 不滿足:number++、count=count *5等
- 滿足:boolean變量、記錄溫度變化的變量等
- 該變量沒有包含在具有其他變量的不變式中
- 不滿足: low<up
總結(jié)
synchronized和volatile的區(qū)別
volatile不需要加鎖,比synchronized更輕量級,不會阻塞線程;從內(nèi)存可見性角度,volatile讀相當(dāng)于加鎖,volatile寫相當(dāng)于解鎖;synchronzied既能夠保障可見性,又能保證原子性,而volatile只能保證可見性,無法保證原子性。
到此這篇關(guān)于Java線程中synchronized和volatile關(guān)鍵字的區(qū)別詳解的文章就介紹到這了,更多相關(guān)synchronized和volatile關(guān)鍵字內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java mockito單元測試實(shí)現(xiàn)過程解析
這篇文章主要介紹了Java mockito單元測試實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08解決springboot項目打成jar包后運(yùn)行時碰到的小坑
這篇文章主要介紹了解決springboot項目打成jar包后運(yùn)行時碰到的小坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02Java中HTTP GET方法調(diào)用帶有body的問題解決
這篇文章主要為大家詳細(xì)介紹了Java如何解決HTTP GET方法調(diào)用帶有body的問題,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考一下2024-02-02