關(guān)于Java中的可見性和有序性問題
Java 如何解決可見性和有序性問題
并發(fā)場景中,因可見性、原子性、有序性導(dǎo)致問題常常導(dǎo)致bug,Java在誕生之初就支持多線程,自然也有針對這三者的技術(shù)方案
今天就學(xué)習(xí)一下Java如何解決其中的可見性和有序性導(dǎo)致的問題,就引來了今天的主角兒——Java內(nèi)存模型
什么是Java內(nèi)存模型
? 導(dǎo)致可見性的原因是緩存,導(dǎo)致有序性的原因是編譯優(yōu)化
解決可見性、有序性最直接的方法就是禁用緩存和編譯優(yōu)化,但這樣會導(dǎo)致性能下降,我們應(yīng)該想到的是如何按需禁用緩存和編譯優(yōu)化。
? Java內(nèi)存模型規(guī)范了JVM如何提供按需禁用緩存和編譯優(yōu)化的方法,這些方法包括volatile、synchronized和final三個關(guān)鍵字以及六項Happens-Before規(guī)則。
使用volatile困惑
volatile關(guān)鍵字在C語言也有,最原始的意義就是禁用CPU緩存。
例如:
volatile int x = 0
表達就是不能使用CPU緩存,必須從內(nèi)存中讀取或者寫入。
看起來沒啥問題,但是在實際使用卻會帶了困惑。
例如:
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
System.out.println(x);
}
}
}假如有A線程調(diào)用writer(),B線程調(diào)用reader(),輸出來的x會是多少呢,在JDK1.5之前,x可能是0或者42,這是因為CPU緩存導(dǎo)致可見性問題,JDK1.5之后Java內(nèi)存模型對volatile語義進行了增強,因此就引出了Happens-Before規(guī)則。
Happens-Before規(guī)則
Happens-Before規(guī)則就是保證前面的一個操作的結(jié)果對后續(xù)操作是可見的。
它約束了編譯器的優(yōu)化行為,雖允許編譯器優(yōu)化,但是要求編譯器優(yōu)化后一定遵守Happens-Before規(guī)則:
規(guī)則一:程序的順序性規(guī)則
? 這條規(guī)則是指在一個線程中,按照程序順序,前面的操作 Happens-Before 于后續(xù)的任意操作。
這還是比較容易理解的,比如剛才那段示例代碼,按照程序的順序,第 6 行代碼 “x = 42;” Happens-Before 于第 7 行代碼 “v = true;”,這就是規(guī)則 1 的內(nèi)容,也比較符合單線程里面的思維:程序前面對某個變量的修改一定是對后續(xù)操作可見的。
規(guī)則二:volatile 變量規(guī)則
? 這條規(guī)則是指對一個 volatile 變量的寫操作, Happens-Before 于后續(xù)對這個 volatile 變量的讀操作,即對后面的讀操作可見。
規(guī)則三:傳遞性
? 這條規(guī)則是指如果A Happens-Before B,B Happens-Before C,那么 A Happens-Before C。
規(guī)則四:管程中鎖的規(guī)則
? 這條規(guī)則是指對一個鎖的解鎖Happens-Before于后續(xù)對這個鎖的加鎖。
? 管程是一種通用的同步原語,在Java中指的就是synchronized,synchronized是Java里對管程的實現(xiàn)。
tip: 在操作系統(tǒng)中,管程的定義如下: 管程是由一組數(shù)據(jù)以及定義在這組數(shù)據(jù)之上的對該組數(shù)據(jù)操作的操作組成的軟件模塊,稱之為管程。
基本特性:
1. 局部于管程的數(shù)據(jù)只能被局部于管程內(nèi)的過程所訪問。
2. 一個進程只有通過調(diào)用管程內(nèi)的過程才能進入管程訪問共享數(shù)據(jù)
3. 每次僅允許一個進程在管程中執(zhí)行某個內(nèi)部過程。
注意:由于管程是一個語言的成分,所以管程的互斥訪問完全由編譯程序在編譯時自動添加,無需程序員關(guān)注
? 管程中的鎖在Java是隱式實現(xiàn)的,加鎖以及釋放鎖都是編譯器幫我們實現(xiàn)的。
synchronized (this) { //此處自動加鎖
// x是共享變量,初始值=10
if (this.x < 12) {
this.x = 12;
}
} //此處自動解鎖規(guī)則五:線程start()規(guī)則
? 這條是關(guān)于線程啟動的,指主線程A啟動子線程B后,子線程B能夠看到主線程在啟動子線程B前的操作。
規(guī)則六:join()規(guī)則
? 這條是關(guān)于線程等待的,它是指主線程A等待子線程B完成(主線程A通過調(diào)用子線程B的join()方法實現(xiàn)),當(dāng)子線程B完成后(主線程A中join()方法返回)
主線程能夠看到子線程的操作。這里的“看到”,指的是對共享變量的操作。
通過調(diào)用子線程B的join()方法實現(xiàn)),當(dāng)子線程B完成后(主線程A中join()方法返回)
主線程能夠看到子線程的操作。這里的“看到”,指的是對共享變量的操作。
到此這篇關(guān)于關(guān)于Java中的可見性和有序性問題的文章就介紹到這了,更多相關(guān)Java可見性和有序性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過Java來測試JSON和Protocol Buffer的傳輸文件大小
這篇文章主要介紹了通過Java來測試JSON和Protocol Buffer的傳輸文件大小,Protocol Buffer(文中簡稱Protobuffer)是谷歌開發(fā)的新的文件傳輸格式,需要的朋友可以參考下2015-12-12
Java利用Spire.XLS for Java實現(xiàn)自動化生成PDF文檔
在 Java 后端高效靈活地實現(xiàn) PDF 文檔的生成,常常是困擾開發(fā)者的一個痛點,本文將為您介紹如何利用 Spire.XLS for Java 輕松駕馭 Java 中的 PDF 文檔生成,感興趣的小伙伴可以了解下2026-02-02
LambdaQueryWrapper與QueryWrapper的使用方式
這篇文章主要介紹了LambdaQueryWrapper與QueryWrapper的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
SpringBoot集成XXL-JOB實現(xiàn)任務(wù)管理全流程
XXL-JOB 是一款輕量級分布式任務(wù)調(diào)度平臺,功能豐富、界面簡潔、易于擴展,本文介紹如何通過 Spring Boot 項目,使用 RestTemplate 和 Feign 的方式調(diào)用 XXL-JOB 后臺管理接口,實現(xiàn)任務(wù)的全生命周期管理,需要的朋友可以參考下2025-08-08

