亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

前言

在任何編程語言中,多線程操作同一個數(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)在已經是500了。

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

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

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

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

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

在這里插入圖片描述

問題分析

之所以會發(fā)生這樣的問題,其實和目前的Java的內存模型有關系。我們知道現(xiàn)在的主機一般都會在CPU和主存之間方置緩存,用來緩沖CPU過快的執(zhí)行速度與主存相對較慢的IO速度。其實Java的線程也有類似的結果,只不過緩存被本地工作空間代替了,類似于下圖。但是要說明一點,我們可以看作每個線程有自己的私有空間(local workspace)和共享空間(main memory) ,實際上Java并沒有在物理內存上這樣劃出來一塊,這只是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)了偏差,后來再經過各個線程的互相刷新共享空間的數(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)下,一個變量的先賦值后運算和先運算后賦值就可能會產生很明顯的區(qū)別。但是對于volatile修飾的變量有這樣一個規(guī)則來保證程序執(zhí)行的順序:

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

最終是如何實現(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ū)別

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

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

總結

本文的主要內容就在于要理解volatile的緩存的一致性協(xié)議導致的共享變量可見性,以及volatile在解析成為匯編語言的時候對變量加鎖兩塊理論內容。

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

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

相關文章

  • 一文掌握JavaWeb登錄認證

    一文掌握JavaWeb登錄認證

    登錄認證是每個系統(tǒng)中必不可少的功能,通過用戶名和密碼來驗證用戶身份,JavaWeb中實現(xiàn)登錄認證通常需要處理HTTP協(xié)議的無狀態(tài)性,涉及會話管理、令牌技術等,本文給大家介紹JavaWeb登錄認證的相關知識,感興趣的朋友跟隨小編一起看看吧
    2024-09-09
  • SpringBoot大學心理服務系統(tǒng)實現(xiàn)流程分步講解

    SpringBoot大學心理服務系統(tǒng)實現(xiàn)流程分步講解

    本系統(tǒng)主要論述了如何使用JAVA語言開發(fā)一個大學生心理服務系統(tǒng) ,本系統(tǒng)將嚴格按照軟件開發(fā)流程進行各個階段的工作,采用B/S架構,面向對象編程思想進行項目開發(fā)
    2022-09-09
  • SpringBoot實現(xiàn)對超大文件進行異步壓縮下載的使用示例

    SpringBoot實現(xiàn)對超大文件進行異步壓縮下載的使用示例

    在Web應用中,文件下載功能是一個常見的需求,本文介紹了SpringBoot實現(xiàn)對超大文件進行異步壓縮下載的使用示例,具有一定的參考價值,感興趣的可以了解一下,
    2023-09-09
  • Netty分布式ByteBuf中PooledByteBufAllocator剖析

    Netty分布式ByteBuf中PooledByteBufAllocator剖析

    這篇文章主要為大家介紹了Netty分布式ByteBuf剖析PooledByteBufAllocator簡述,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • MyBatisPlus 一對多、多對一、多對多的完美解決方案

    MyBatisPlus 一對多、多對一、多對多的完美解決方案

    這篇文章主要介紹了MyBatisPlus 一對多、多對一、多對多的完美解決方案,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • idea中引入了gb2312編碼的文件的解決方法

    idea中引入了gb2312編碼的文件的解決方法

    這篇文章主要介紹了idea中引入了gb2312編碼的文件的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-08-08
  • Mybatis分頁插件PageHelper的配置和簡單使用方法(推薦)

    Mybatis分頁插件PageHelper的配置和簡單使用方法(推薦)

    在使用Java Spring開發(fā)的時候,Mybatis算是對數(shù)據(jù)庫操作的利器了。這篇文章主要介紹了Mybatis分頁插件PageHelper的配置和使用方法,需要的朋友可以參考下
    2017-12-12
  • Java內存劃分:運行時數(shù)據(jù)區(qū)域

    Java內存劃分:運行時數(shù)據(jù)區(qū)域

    聽說Java運行時環(huán)境的內存劃分是挺進BAT的必經之路,這篇文章主要給大家介紹了關于Java運行時數(shù)據(jù)區(qū)域(內存劃分)的相關資料,需要的朋友可以參考下
    2021-07-07
  • Java使用CountDownLatch實現(xiàn)網絡同步請求的示例代碼

    Java使用CountDownLatch實現(xiàn)網絡同步請求的示例代碼

    CountDownLatch 是一個同步工具類,用來協(xié)調多個線程之間的同步,它能夠使一個線程在等待另外一些線程完成各自工作之后,再繼續(xù)執(zhí)行。被將利用CountDownLatch實現(xiàn)網絡同步請求,異步同時獲取商品信息組裝,感興趣的可以了解一下
    2023-01-01
  • idea左側的commit框設置顯示出來方式

    idea左側的commit框設置顯示出來方式

    在IDEA中顯示左側的commit框,首先通過File-Settings-Version Control-Commit進行設置,然后勾選Use non-modal commit interface完成
    2025-01-01

最新評論