最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中的volatile實現(xiàn)機制詳細解析

 更新時間:2024年01月30日 08:30:43   作者:Smallc0de  
這篇文章主要介紹了Java中的volatile實現(xiàn)機制詳細解析,本文的主要內(nèi)容就在于要理解volatile的緩存的一致性協(xié)議導致的共享變量可見性,以及volatile在解析成為匯編語言的時候?qū)ψ兞考渔i兩塊理論內(nèi)容,需要的朋友可以參考下

前言

在任何編程語言中,多線程操作同一個數(shù)據(jù)都會帶來數(shù)據(jù)不一致的問題,這是由于在多線程的情況下CPU分配時間片并不是按照線程創(chuàng)建順序去分配的,具有一定的隨機性。

一個任務被首先創(chuàng)建出來,并不意味著這個特定的任務一定會首先執(zhí)行,為了解決并發(fā)狀態(tài)下數(shù)據(jù)不一致的問題,就有了Lock、synchronized、volatile等等一系列的解決方法。

線程對資源的感知

我們現(xiàn)在有一個游戲的充錢系統(tǒng),這個系統(tǒng)只有兩種功能:充錢、顯示余額。我們希望這個系統(tǒng)的功能是這樣的,如果玩家一旦充錢,賬戶變化立刻會被感知到并輸出出來。假設賬戶里有0元:

public class VolatileTest {
   final static int MAX = 500;    //最多500元作為退出條件
    static int deposit = 0;       //初始余額
    public static void main(String[] args) {
        //顯示賬戶余額線程
        new Thread(() -> {
            int calculate = deposit;
            while (calculate < MAX) {
                if(calculate!=deposit){ //當發(fā)現(xiàn)本地變量和全局變量不一致時輸出
                    System.out.println("當前余額" + deposit);
                    calculate = deposit;
                }
            }
        }).start();
        //充錢線程,每次充錢100元
        new Thread(() -> {
            int calculate = deposit;
            while (calculate < MAX) {
                calculate += 100; //改變金額
                System.out.println("充錢100元,當前總額" + calculate);
                deposit = calculate; //回寫給全局變量
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

運行程序,拿到下面的結果:

在這里插入圖片描述

結果發(fā)現(xiàn),我們的感知線程除了第一次感知到變化外,后續(xù)的充錢都沒有感知到。但是線程一直再運行并沒有停下。

我們接著分析代碼邏輯,充錢線程之所以不再打印了是因為超出了充錢最大限制while (calculate < MAX),那么理論上來說deposit現(xiàn)在已經(jīng)是500了。

但是在顯示余額的線程上,并沒有感知到deposit被修改,仍然認為while (calculate < MAX)條件依然成立。

在這個顯示線程中calculate==deposit==100,所以顯示線程會一直空轉(zhuǎn),直到把CPU資源消耗完畢崩潰才能停下來,這顯然是一個非常壞的結果。

如何修改程序呢,其實也非常簡單,只要在初始余額變量上加上volatile關鍵字即可。

static volatile int deposit = 0;       //初始余額,加上關鍵字

重新運行輸出結果,這次不僅程序運行符合預期,而且程序順利執(zhí)行完畢。每次充錢的線程執(zhí)行完畢,查詢余額的線程立刻感知到并且執(zhí)行了相應的邏輯,整個程序邏輯上執(zhí)行順利,結果符合預期。

在這里插入圖片描述

問題分析

之所以會發(fā)生這樣的問題,其實和目前的Java的內(nèi)存模型有關系。我們知道現(xiàn)在的主機一般都會在CPU和主存之間方置緩存,用來緩沖CPU過快的執(zhí)行速度與主存相對較慢的IO速度。其實Java的線程也有類似的結果,只不過緩存被本地工作空間代替了,類似于下圖。但是要說明一點,我們可以看作每個線程有自己的私有空間(local workspace)和共享空間(main memory) ,實際上Java并沒有在物理內(nèi)存上這樣劃出來一塊,這只是Java執(zhí)行中的一個概念模型。物理上仍然是CPU – Cache – Memory這樣的結構。

在這里插入圖片描述

在這種結構中,主存中的數(shù)據(jù)每個線程都可以訪問,但是本地工作空間只有本地線程可以訪問。

而本地工作空間中方置的東西就是本地變量和主存資源副本,因此線程并不能直接修改修改主存的數(shù)據(jù),只能讀取到工作空間中,然后由線程拿到CPU中修改。修改完成后,再從私有工作空間刷新回主存。這樣所有的線程就可以拿到最新的數(shù)據(jù)到自己的工作空間里進行操作,這個邏輯在單線程下自然沒有問題。

但是多線程的時候,就會出現(xiàn)數(shù)據(jù)不一致的問題,比如Thread 0在私有工作空間中做了deposit變量修改,但是還沒有刷新到主存中的時候。

Thread 1就把主存中的deposit變量copy到了自己的工作空間進行操作,那么Thread 1用的就是舊的數(shù)據(jù),計算的結果也就出現(xiàn)了偏差,后來再經(jīng)過各個線程的互相刷新共享空間的數(shù)據(jù),偏差就會越來越大。

volatile的原理

根據(jù)我們寫的例子來看,volatile關鍵字加上以后,就可以保證當某個線程對主存中數(shù)據(jù)修改的時候,其他線程能夠感知到這種修改,因此可以基于最新的版本進行后續(xù)的操作。

所以volatile關鍵字應該具有以下用作:

保證數(shù)據(jù)的可見性

某個線程對共享數(shù)據(jù)的修改,其他數(shù)據(jù)能夠立刻感知到,這點是如何做到的呢?首先要先引入一個知識點:緩存的一致性協(xié)議(MESI)。

這個協(xié)議會使得:

讀操作,CPU讀取cache中的數(shù)據(jù)時,不做任何鎖操作。

寫操作,當CPU將要修改某個共享變量的時候,CPU會發(fā)出信號,通知其他的CPU將該變量在其他中緩存中副本所對應的cache line置為無效,這也就導致了其他CPU的緩存中該變量失效,只能再次從主存中讀取。

在這里插入圖片描述

有了這個前提知識以后,有些讀者一定想到了:一旦給某個共享變量加上volatile關鍵字以后,當某個線程要修改共享變量的時候,會通知其他線程,來把其他線程的私有空間中的該共享變量的副本的cache line置為無效,使得其他線程再次去主存中讀取最新的值,其對應的硬件原理就是上面所說的MESI協(xié)議。通過這樣的辦法,保證了共享變量在各個線程中的修改可見性,使得所有的線程對共享變量的修改具有感知。但是重新讀取并不能可以保證一定可以讀取到最新的數(shù)據(jù),因此volatile還必須要有更多的功能。

保證線程的有序性

程序在編譯階段和指令優(yōu)化階段會對執(zhí)行的指令進行重排序,也就是說我們寫的代碼順序,并不是程序的執(zhí)行順序。這樣做的目的是為了提高CPU的執(zhí)行效率和吞吐量,比如賦值指令的執(zhí)行效率明顯會遠遠高于運算指令,那么在重排序階段就會把賦值指令放在一起,運算指令放在一起,以提高總體的效率。這樣做在單線程狀態(tài)下沒有問題,但是在多線程狀態(tài)下,一個變量的先賦值后運算和先運算后賦值就可能會產(chǎn)生很明顯的區(qū)別。但是對于volatile修飾的變量有這樣一個規(guī)則來保證程序執(zhí)行的順序:

  • volatile之前的代碼不能調(diào)整到它的后面。
  • volatile之后的代碼不能調(diào)整到它的前面。
  • volatile修飾的代碼,不可以調(diào)整順序。

最終是如何實現(xiàn)這個功能的呢?當解析到被volatile修飾的變量的時候,在匯編代碼上該變量會有一個Lock標記,表示該變量被鎖住。也就是說想某一個線程使用共享變量的時候,該變量就會被鎖住。其他線程由于MESI導致必須去主存哪取新數(shù)據(jù)的時候,會因為這里有Lock而阻塞,直到這個線程釋放該共享變量。分析到最后其實volatile依然是由鎖構建的功能,但是這個鎖也是一個輕量級的鎖。

注意:volatile即使具有上述這些作用,但是并不具有原子性。

volatile的應用

說了半天volatile關鍵字的原理,這里列舉幾個常用的場景。

Flag標志:作為控制某個功能或者分支的flag標志使用。

public class VolatileTest implements Runnable{
    private volatile boolean flag=false;
    @Override
    public void run() {
        if (flag){
            //...code
        }else{
            //...code
        }
    }
    public void close(){
        flag=false;
    }
}

雙重檢查鎖定:Double Checked Locking(DCL),一般用在單例模式上。

public class VolatileTest2 {
    private volatile static VolatileTest2 vt;
    public static VolatileTest2 getInstance(){
        if(Objects.isNull(vt)){
            vt=new VolatileTest2();
        }
        return vt;
    }
}

程序的執(zhí)行順序必須保證:如果有場景中必須要求某些變量在程序的限定位置出現(xiàn),而且不能隨意變更執(zhí)行順序,那么可以對這些變量加上volatile去保證,這些代碼的執(zhí)行順序是完全按照代碼所寫的順序執(zhí)行的。

volatile與synchronized的區(qū)別

之前的博客已經(jīng)對synchronized做了講解,我們知道synchronized是加鎖用的,那么volatile和synchronized有什么區(qū)別呢?我們用一個表格做個對比。

區(qū)別volatilesynchronized
語法上只能修飾變量只能修飾方法和語句塊
原子性不能保證原子性可以保證原子性
可見性通過對變量加lock,使用緩存的一致性協(xié)議保證可見性使用對象監(jiān)視器monitor保證可見性,monitorenter,monitorexit,ACC_SYNCHRONIZED
有序性可以保證有序性可以保證有序性,但是加鎖部分變?yōu)閱尉€程執(zhí)行
阻塞輕量級鎖不會阻塞偏向鎖,可能會引發(fā)阻塞; 重量級鎖,會引起阻塞

總結

本文的主要內(nèi)容就在于要理解volatile的緩存的一致性協(xié)議導致的共享變量可見性,以及volatile在解析成為匯編語言的時候?qū)ψ兞考渔i兩塊理論內(nèi)容。

到此volatile關鍵字的基本內(nèi)容告一段落,謝謝大家。

到此這篇關于Java中的volatile實現(xiàn)機制詳細解析的文章就介紹到這了,更多相關volatile實現(xiàn)機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 基于獲取JAVA路徑,包括CLASSPATH外的路徑的方法詳解

    基于獲取JAVA路徑,包括CLASSPATH外的路徑的方法詳解

    本篇文章是對獲取JAVA路徑,包括CLASSPATH外的路徑的方法進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • spring事務之事務掛起和事務恢復源碼解讀

    spring事務之事務掛起和事務恢復源碼解讀

    這篇文章主要介紹了spring事務之事務掛起和事務恢復源碼解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Mybatis框架之工廠模式(Factory Pattern)

    Mybatis框架之工廠模式(Factory Pattern)

    MyBatis中使用工廠模式來管理和創(chuàng)建SqlSession對象,從而簡化數(shù)據(jù)庫訪問的配置和管理過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-11-11
  • maven打包所有依賴對外提供sdk.jar

    maven打包所有依賴對外提供sdk.jar

    這篇文章主要介紹了maven打包所有依賴,對外提供sdk.jar,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • Java?深入理解創(chuàng)建型設計模式之抽象工廠模式

    Java?深入理解創(chuàng)建型設計模式之抽象工廠模式

    當系統(tǒng)所提供的工廠所需生產(chǎn)的具體產(chǎn)品并不是一個簡單的對象,而是多個位于不同產(chǎn)品等級結構中屬于不同類型的具體產(chǎn)品時需要使用抽象工廠模式,抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態(tài)
    2022-02-02
  • Java后端SSM框架圖片上傳功能實現(xiàn)方法解析

    Java后端SSM框架圖片上傳功能實現(xiàn)方法解析

    這篇文章主要介紹了Java后端SSM框架圖片上傳功能實現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • 關于Springboot在新增和修改下上傳圖片并顯示的問題

    關于Springboot在新增和修改下上傳圖片并顯示的問題

    這篇文章主要介紹了關于Springboot在新增和修改下上傳圖片并顯示的問題及解決方法,在這里 springboot中已經(jīng)內(nèi)嵌了上傳圖片的依賴包,因此不需要再添加額外依賴,具體實現(xiàn)代碼跟隨小編一起看看吧
    2021-04-04
  • SpringCloud使用Zookeeper作為注冊中心

    SpringCloud使用Zookeeper作為注冊中心

    這篇文章主要介紹了SpringCloud如何使用Zookeeper作為注冊中心,幫助大家更好的理解和學習使用Zookeeper,感興趣的朋友可以了解下
    2021-04-04
  • Java設計模式之外觀模式(Facade模式)介紹

    Java設計模式之外觀模式(Facade模式)介紹

    這篇文章主要介紹了Java設計模式之外觀模式(Facade模式)介紹,外觀模式(Facade)的定義:為子系統(tǒng)中的一組接口提供一個一致的界面,需要的朋友可以參考下
    2015-03-03
  • springboot接收日期字符串參數(shù)與返回日期字符串類型格式化

    springboot接收日期字符串參數(shù)與返回日期字符串類型格式化

    這篇文章主要介紹了springboot接收日期字符串參數(shù)與返回日期字符串類型格式化,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01

最新評論

镇康县| 保山市| 通海县| 驻马店市| 老河口市| 井冈山市| 巴塘县| 抚宁县| 视频| 闽清县| 新泰市| 甘洛县| 云和县| 全椒县| 巫山县| 海兴县| 棋牌| 霍林郭勒市| 台北县| 根河市| 富锦市| 嵊泗县| 盱眙县| 延寿县| 米脂县| 成都市| 临朐县| 信阳市| 青田县| 芮城县| 疏附县| 吉隆县| 信宜市| 永丰县| 南涧| 永平县| 昌宁县| 蒙阴县| 深州市| 镇雄县| 永春县|