關(guān)于Java中的可見性和有序性問題
Java 如何解決可見性和有序性問題
并發(fā)場(chǎng)景中,因可見性、原子性、有序性導(dǎo)致問題常常導(dǎo)致bug,Java在誕生之初就支持多線程,自然也有針對(duì)這三者的技術(shù)方案
今天就學(xué)習(xí)一下Java如何解決其中的可見性和有序性導(dǎo)致的問題,就引來了今天的主角兒——Java內(nèi)存模型
什么是Java內(nèi)存模型
? 導(dǎo)致可見性的原因是緩存,導(dǎo)致有序性的原因是編譯優(yōu)化
解決可見性、有序性最直接的方法就是禁用緩存和編譯優(yōu)化,但這樣會(huì)導(dǎo)致性能下降,我們應(yīng)該想到的是如何按需禁用緩存和編譯優(yōu)化。
? Java內(nèi)存模型規(guī)范了JVM如何提供按需禁用緩存和編譯優(yōu)化的方法,這些方法包括volatile、synchronized和final三個(gè)關(guān)鍵字以及六項(xiàng)Happens-Before規(guī)則。
使用volatile困惑
volatile關(guān)鍵字在C語言也有,最原始的意義就是禁用CPU緩存。
例如:
volatile int x = 0
表達(dá)就是不能使用CPU緩存,必須從內(nèi)存中讀取或者寫入。
看起來沒啥問題,但是在實(shí)際使用卻會(huì)帶了困惑。
例如:
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會(huì)是多少呢,在JDK1.5之前,x可能是0或者42,這是因?yàn)镃PU緩存導(dǎo)致可見性問題,JDK1.5之后Java內(nèi)存模型對(duì)volatile語義進(jìn)行了增強(qiáng),因此就引出了Happens-Before規(guī)則。
Happens-Before規(guī)則
Happens-Before規(guī)則就是保證前面的一個(gè)操作的結(jié)果對(duì)后續(xù)操作是可見的。
它約束了編譯器的優(yōu)化行為,雖允許編譯器優(yōu)化,但是要求編譯器優(yōu)化后一定遵守Happens-Before規(guī)則:
規(guī)則一:程序的順序性規(guī)則
? 這條規(guī)則是指在一個(gè)線程中,按照程序順序,前面的操作 Happens-Before 于后續(xù)的任意操作。
這還是比較容易理解的,比如剛才那段示例代碼,按照程序的順序,第 6 行代碼 “x = 42;” Happens-Before 于第 7 行代碼 “v = true;”,這就是規(guī)則 1 的內(nèi)容,也比較符合單線程里面的思維:程序前面對(duì)某個(gè)變量的修改一定是對(duì)后續(xù)操作可見的。
規(guī)則二:volatile 變量規(guī)則
? 這條規(guī)則是指對(duì)一個(gè) volatile 變量的寫操作, Happens-Before 于后續(xù)對(duì)這個(gè) volatile 變量的讀操作,即對(duì)后面的讀操作可見。
規(guī)則三:傳遞性
? 這條規(guī)則是指如果A Happens-Before B,B Happens-Before C,那么 A Happens-Before C。
規(guī)則四:管程中鎖的規(guī)則
? 這條規(guī)則是指對(duì)一個(gè)鎖的解鎖Happens-Before于后續(xù)對(duì)這個(gè)鎖的加鎖。
? 管程是一種通用的同步原語,在Java中指的就是synchronized,synchronized是Java里對(duì)管程的實(shí)現(xiàn)。
tip: 在操作系統(tǒng)中,管程的定義如下: 管程是由一組數(shù)據(jù)以及定義在這組數(shù)據(jù)之上的對(duì)該組數(shù)據(jù)操作的操作組成的軟件模塊,稱之為管程。
基本特性:
1. 局部于管程的數(shù)據(jù)只能被局部于管程內(nèi)的過程所訪問。
2. 一個(gè)進(jìn)程只有通過調(diào)用管程內(nèi)的過程才能進(jìn)入管程訪問共享數(shù)據(jù)
3. 每次僅允許一個(gè)進(jìn)程在管程中執(zhí)行某個(gè)內(nèi)部過程。
注意:由于管程是一個(gè)語言的成分,所以管程的互斥訪問完全由編譯程序在編譯時(shí)自動(dòng)添加,無需程序員關(guān)注
? 管程中的鎖在Java是隱式實(shí)現(xiàn)的,加鎖以及釋放鎖都是編譯器幫我們實(shí)現(xiàn)的。
synchronized (this) { //此處自動(dòng)加鎖 // x是共享變量,初始值=10 if (this.x < 12) { this.x = 12; } } //此處自動(dòng)解鎖
規(guī)則五:線程start()規(guī)則
? 這條是關(guān)于線程啟動(dòng)的,指主線程A啟動(dòng)子線程B后,子線程B能夠看到主線程在啟動(dòng)子線程B前的操作。
規(guī)則六:join()規(guī)則
? 這條是關(guān)于線程等待的,它是指主線程A等待子線程B完成(主線程A通過調(diào)用子線程B的join()方法實(shí)現(xiàn)),當(dāng)子線程B完成后(主線程A中join()方法返回)
主線程能夠看到子線程的操作。這里的“看到”,指的是對(duì)共享變量的操作。
通過調(diào)用子線程B的join()方法實(shí)現(xiàn)),當(dāng)子線程B完成后(主線程A中join()方法返回)
主線程能夠看到子線程的操作。這里的“看到”,指的是對(duì)共享變量的操作。
到此這篇關(guān)于關(guān)于Java中的可見性和有序性問題的文章就介紹到這了,更多相關(guān)Java可見性和有序性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文掌握SpringSecurity?BCrypt密碼加密和解密
BCrypt就是一款加密工具,可以比較方便地實(shí)現(xiàn)數(shù)據(jù)的加密工作。也可以簡(jiǎn)單理解為它內(nèi)部自己實(shí)現(xiàn)了隨機(jī)加鹽處理,這篇文章主要介紹了SpringSecurity?BCrypt密碼加密和解密,一文學(xué)會(huì)使用BCryptPasswordEncoder的方法,需要的朋友可以參考下2023-04-04詳解SpringBoot啟動(dòng)代碼和自動(dòng)裝配源碼分析
這篇文章主要介紹了SpringBoot啟動(dòng)代碼和自動(dòng)裝配源碼分析,使用SpringBoot很簡(jiǎn)單,在主類中添加一個(gè)@SpringBootApplication,以及調(diào)用SpringApplication.run()并傳入主類,本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07ConcurrentHashMap線程安全及實(shí)現(xiàn)原理實(shí)例解析
這篇文章主要介紹了ConcurrentHashMap線程安全及實(shí)現(xiàn)原理實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11解決SpringBoot中MultipartResolver和ServletFileUpload的沖突問題
這篇文章主要介紹了解決SpringBoot中MultipartResolver和ServletFileUpload的沖突問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Java多線程程序中synchronized修飾方法的使用實(shí)例
synchronized關(guān)鍵字主要北用來進(jìn)行線程同步,這里我們主要來演示Java多線程程序中synchronized修飾方法的使用實(shí)例,需要的朋友可以參考下:2016-06-06基于Beanutils.copyProperties()的用法及重寫提高效率
這篇文章主要介紹了Beanutils.copyProperties( )的用法及重寫提高效率的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09