欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析Java中volatile的底層原理

 更新時(shí)間:2023年07月11日 11:14:23   作者:賢子磊  
這篇文章主要介紹了深入解析Java中volatile的底層原理,volatile關(guān)鍵字用于保證變量的可見性和禁止指令重排序,即當(dāng)一個(gè)線程修改了volatile變量的值,其他線程能夠立即看到這個(gè)變量的最新值,而不是使用緩存中的舊值,需要的朋友可以參考下

一、前言

之前我們學(xué)習(xí)過synchronized,知道它是一個(gè)重量級的鎖,雖然jdk1.6對其做了很大的優(yōu)化,但是成本還是較高。因此Java另一個(gè)關(guān)鍵字閃亮登場——volatile。volatile又被稱為輕量級的synchronized,它在多處理器中保證了共享變量的可見性。volatile變量修飾符如果使用恰當(dāng)?shù)脑?,它比synchronized的使用和執(zhí)行成本會(huì)更低。下面我們將深入剖析volatile的實(shí)現(xiàn)原理。

二、什么是volatile

Java語言規(guī)范第3版中對volatile的定義如下

Java編程語言允許線程訪問共享變量,為了確保共享變量能被準(zhǔn)確和一致地更新,線程應(yīng)該確保通過排他鎖單獨(dú)獲得這個(gè)變量。Java語言提供了volatile,在某些情況下比鎖要更加方便。如果一個(gè)字段被聲明成volatile,Java線程內(nèi)存模型確保所有線程看到這個(gè)變量的值是一致的。

三、volatile的語義

當(dāng)一個(gè)共享變量被volatile修飾之后,這個(gè)變量就具備了兩層語義:

  • 保證共享變量的可見性:保證了不同線程對這個(gè)變量進(jìn)行操作時(shí)的可見性,即一個(gè)線程修改了某個(gè)變量的值,這新值對其他線程來說是立即可見的。
  • 防止局部指令重排序:happens-before規(guī)則中的volatile變量規(guī)則規(guī)定了一個(gè)線程先去寫一個(gè)volatile變量,然后一個(gè)線程去讀這個(gè)變量,那么這個(gè)寫操作的結(jié)果一定對讀的這個(gè)線程可見。

四、volatile的使用

1. volatile保證共享變量的可見性

public class MyTest {
    public static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
        //線程1
        Thread thread1 = new Thread(() -> {
            while (!stop) {
                //do something...
            }
        });
        //線程2
        Thread thread2 = new Thread(() -> doStop());
        thread1.start();
        //這里的睡眠是保證線程1先執(zhí)行
        Thread.sleep(1000);
        thread2.start();
    }
    public static void doStop() {
        stop = true;
    }
}

如上所示的代碼,主要用于線程2去利用共享變量stop去中斷線程1的執(zhí)行。正常情況下如果線程2先執(zhí)行,線程1再執(zhí)行不會(huì)有問題,線程2能夠正常結(jié)束線程1的執(zhí)行,但是上述的代碼會(huì)一直死循環(huán)無法結(jié)束。

這是因?yàn)榫€程1先執(zhí)行,此時(shí)stop的值初始為false,線程1執(zhí)行死循環(huán)。根據(jù)Java內(nèi)存模型,每個(gè)線程不能直接訪問主內(nèi)存的,需另拷貝一份到自己的工作內(nèi)存中。因此線程1執(zhí)行的時(shí)候會(huì)拷貝stop的副本到自己的工作空間內(nèi),而線程2執(zhí)行的時(shí)候雖然修改了stop的值,但是線程1感知不到,因此會(huì)一直死循環(huán)

因此我們可以使用volatile關(guān)鍵字修改stop變量

public static volatile boolean stop =false;

這里的volatile有兩個(gè)作用

  • 被volatile關(guān)鍵字修飾的變量修改時(shí)會(huì)立即寫入主內(nèi)存
  • 被volatile關(guān)鍵字修飾的變量修改時(shí),其余線程存放的該變量的副本會(huì)被置為無效

因此當(dāng)線程2修改stop的值的時(shí)候,會(huì)立即寫回主內(nèi)存,同時(shí)使線程1的工作內(nèi)存中的stop變量置為無效。線程1在每次循環(huán)讀取stop的時(shí)候發(fā)現(xiàn)自己的stop變量無效了,會(huì)重新去主內(nèi)存讀取最新的stop的值,即讀取到線程2修改過的最新值,程序正常結(jié)束。

2. volatile保證一定程度的有序性

//線程1:
context = loadContext();   //語句1
inited = true;             //語句2
//線程2:
while(!inited ){
    sleep()
}
doSomethingwithconfig(context);

借用網(wǎng)上的一個(gè)經(jīng)典案例,線程1負(fù)責(zé)初始化上下文,線程2則是線程1初始化結(jié)束后進(jìn)行的后續(xù)操作。我們知道處理器為了提高程序運(yùn)行效率,可能會(huì)對輸入代碼進(jìn)行重排優(yōu)化,其中語句1和語句2不存在依賴關(guān)系,因此處理器可能會(huì)將語句1和語句2進(jìn)行調(diào)換順序,即先執(zhí)行語句2再執(zhí)行語句1,那么如果線程1先執(zhí)行完語句2,還沒有來得及執(zhí)行語句1的時(shí)候,線程開始執(zhí)行,發(fā)現(xiàn)inited已經(jīng)為true,它就會(huì)認(rèn)為上下文已經(jīng)被初始化完成了,從而會(huì)調(diào)用doSomethingwithconfig方法,此時(shí)就會(huì)發(fā)生錯(cuò)誤。因此我們可以使用volatile關(guān)鍵字修改inited變量,這樣會(huì)禁止inited語句前后的重排序,從而保證了線程安全性。

五、volatile的實(shí)現(xiàn)機(jī)制和原理

通過前面對volatile的語義的使用的介紹,相信大家已經(jīng)有了一個(gè)初步的了解,但是volatile為什么會(huì)實(shí)現(xiàn)這些特性呢?接來下我們就正式進(jìn)入volatile底層原理的講解。

1. 內(nèi)存屏障

在介紹原理前,我們先了解一下內(nèi)存屏障。

1.1 什么是內(nèi)存屏障

  • 內(nèi)存屏障(memory barrier)是一個(gè)CPU指令。這條指令可以確保一些特定指令的執(zhí)行順序,影響一些數(shù)據(jù)的可見性(可能是某些指令執(zhí)行后的結(jié)果)。
  • 插入一個(gè)內(nèi)存屏障,相當(dāng)于告訴CPU和編譯器先于這個(gè)命令的必須先執(zhí)行,后于這個(gè)命令的必須后執(zhí)行。內(nèi)存屏障另一個(gè)作用是強(qiáng)制更新一次不同CPU的緩存

1.2 內(nèi)存屏障的分類

  • LoadLoad屏障
Load1; 
LoadLoad屏障; 
Load2;

Load1Load2 代表兩條讀取指令。在Load2要讀取的數(shù)據(jù)被訪問前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。

  • StoreStore屏障
Store1;
StoreStore屏障;
Store2;

Store1Store2代表兩條寫入指令。在Store2寫入執(zhí)行前,保證Store1的寫入操作對其它處理器可見

  • LoadStore屏障
Load1;
LoadStore屏障;
Store2;

在Store2被寫入前,保證Load1要讀取的數(shù)據(jù)被讀取完畢

  • StoreLoad屏障
Store1;
StoreLoad屏障;
Load2;

在Load2讀取操作執(zhí)行前,保證Store1的寫入對所有處理器可見。StoreLoad屏障的開銷是四種屏障中最大的。

2. 實(shí)現(xiàn)機(jī)制

從代碼的層面我們看不到volatile的實(shí)現(xiàn)機(jī)制,因此我們需要從匯編指令的層次進(jìn)行研究,我們查看一下第四章第一節(jié)代碼中doStop方法的匯編指令

# 可以看到此時(shí)有一個(gè)lock前綴指令
0x0000000003226f6e: lock add dword ptr [rsp],0h  ;*putstatic stop
                                                ; - com.jicl.MyTest::doStop@1 (line 35)

lock前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄),它會(huì)提供3個(gè)功能

確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成;

強(qiáng)制將對緩存的修改操作立即寫入主存,利用緩存一致性機(jī)制,并且緩存一致性機(jī)制會(huì)阻止同時(shí)修改由兩個(gè)以上CPU緩存的內(nèi)存區(qū)域數(shù)據(jù)。

如果是寫操作,它會(huì)導(dǎo)致其他CPU中對應(yīng)的緩存行無效。

volatile的底層實(shí)現(xiàn)是通過插入內(nèi)存屏障,但是對于編譯器來說,發(fā)現(xiàn)一個(gè)最優(yōu)布置來最小化插入內(nèi)存屏障的總數(shù)幾乎是不可能的,所以,JMM采用了保守策略。如下:

  • 在每一個(gè)volatile寫操作前面插入一個(gè)StoreStore屏障:保證在volatile寫之前,其前面的所有普通寫操作都已經(jīng)刷新到主內(nèi)存中
  • 在每一個(gè)volatile寫操作后面插入一個(gè)StoreLoad屏障:避免volatile寫與后面可能有的volatile讀/寫操作重排序
  • 在每一個(gè)volatile讀操作后面插入一個(gè)LoadLoad屏障:禁止處理器把上面的volatile讀與下面的普通讀重排序
  • 在每一個(gè)volatile讀操作后面插入一個(gè)LoadStore屏障:禁止處理器把上面的volatile讀與下面的普通寫重排序

3. 實(shí)現(xiàn)原理

在介紹完volatile的底層實(shí)現(xiàn)機(jī)制,我們來分析volatile是如何實(shí)現(xiàn)可見性和有序性的

3.1 可見性

如果對聲明了volatile變量進(jìn)行寫操作時(shí),JVM會(huì)向處理器發(fā)送一條Lock前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫會(huì)到系統(tǒng)內(nèi)存。這一步確保了如果有其他線程對聲明了volatile變量進(jìn)行修改,則立即更新主內(nèi)存中數(shù)據(jù)。但這時(shí)候其他處理器的緩存還是舊的,所以在多處理器環(huán)境下,為了保證各個(gè)處理器緩存一致,每個(gè)處理會(huì)通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己的緩存是否過期,當(dāng)處理器發(fā)現(xiàn)自己緩存行對應(yīng)的內(nèi)存地址被修改了,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無效狀態(tài),當(dāng)處理器要對這個(gè)數(shù)據(jù)進(jìn)行修改操作時(shí),會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存把數(shù)據(jù)讀到處理器緩存里。 這一步確保了其他線程獲得的聲明了volatile變量都是從主內(nèi)存中獲取最新的。

3.2 有序性

Lock前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄),它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成。

到此這篇關(guān)于深入解析Java中volatile的底層原理的文章就介紹到這了,更多相關(guān)volatile的底層原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決SpringBoot集成Eureka導(dǎo)致返回結(jié)果由json變?yōu)閤ml的問題

    解決SpringBoot集成Eureka導(dǎo)致返回結(jié)果由json變?yōu)閤ml的問題

    這篇文章主要介紹了解決SpringBoot集成Eureka導(dǎo)致返回結(jié)果由json變?yōu)閤ml的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring Cache的使用示例詳解

    Spring Cache的使用示例詳解

    SpringCache是構(gòu)建在SpringContext基礎(chǔ)上的緩存實(shí)現(xiàn),提供了多種緩存注解,如@Cachable、@CacheEvict、@CachePut等,本文通過實(shí)例代碼介紹了Spring Cache的使用,感興趣的朋友一起看看吧
    2025-01-01
  • SpringBoot2.1 RESTful API項(xiàng)目腳手架(種子)項(xiàng)目

    SpringBoot2.1 RESTful API項(xiàng)目腳手架(種子)項(xiàng)目

    這篇文章主要介紹了SpringBoot2.1 RESTful API項(xiàng)目腳手架(種子)項(xiàng)目,用于搭建RESTful API工程的腳手架,只需三分鐘你就可以開始編寫業(yè)務(wù)代碼,不再煩惱于構(gòu)建項(xiàng)目與風(fēng)格統(tǒng)一,感興趣的小伙伴們可以參考一下
    2018-12-12
  • SpringBoot?項(xiàng)目打成?jar后加載外部配置文件的操作方法

    SpringBoot?項(xiàng)目打成?jar后加載外部配置文件的操作方法

    這篇文章主要介紹了SpringBoot?項(xiàng)目打成?jar后加載外部配置文件的操作方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-03-03
  • springboot接口服務(wù),防刷、防止請求攻擊,AOP實(shí)現(xiàn)方式

    springboot接口服務(wù),防刷、防止請求攻擊,AOP實(shí)現(xiàn)方式

    本文介紹了如何使用AOP防止Spring?Boot接口服務(wù)被網(wǎng)絡(luò)攻擊,通過在pom.xml中加入AOP依賴,創(chuàng)建自定義注解類和AOP切面,以及在業(yè)務(wù)類中使用這些注解,可以有效地對接口進(jìn)行保護(hù),測試表明,這種方法有效地防止了網(wǎng)絡(luò)攻擊
    2024-11-11
  • Java Springboot之Spring家族的技術(shù)體系

    Java Springboot之Spring家族的技術(shù)體系

    今天帶大家來學(xué)習(xí)Spring家族的技術(shù)體系,文中有非常詳細(xì)的圖文介紹及代碼示例,對正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下
    2021-05-05
  • Java基礎(chǔ)教程之List集合的常用方法

    Java基礎(chǔ)教程之List集合的常用方法

    這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)教程之List集合的常用方法,在Java編程中List集合是一種常用的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)一組元素,有時(shí)候我們需要對List集合中的元素進(jìn)行分組操作,即將相同屬性或特征的元素歸類到一組,需要的朋友可以參考下
    2023-10-10
  • Eclipse插件開發(fā)實(shí)現(xiàn)控制臺(tái)輸出信息的方法

    Eclipse插件開發(fā)實(shí)現(xiàn)控制臺(tái)輸出信息的方法

    今天小編就為大家分享一篇關(guān)于Eclipse插件開發(fā)實(shí)現(xiàn)控制臺(tái)輸出信息的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • springboot項(xiàng)目如何設(shè)置session的過期時(shí)間

    springboot項(xiàng)目如何設(shè)置session的過期時(shí)間

    這篇文章主要介紹了springboot項(xiàng)目如何設(shè)置session的過期時(shí)間,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java 常見異常(Runtime Exception )詳細(xì)介紹并總結(jié)

    Java 常見異常(Runtime Exception )詳細(xì)介紹并總結(jié)

    這篇文章主要介紹了Java 常見異常(Runtime Exception )詳細(xì)介紹并相關(guān)資料,大家在開發(fā)Java 應(yīng)用軟件的時(shí)候經(jīng)常會(huì)遇到各種異常這里幫大家整理了一部分,并解釋如何解決,需要的朋友可以參考下
    2016-10-10

最新評論