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

Java中的volatile實(shí)現(xiàn)機(jī)制詳細(xì)解析

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

前言

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

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

線(xiàn)程對(duì)資源的感知

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

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

運(yùn)行程序,拿到下面的結(jié)果:

在這里插入圖片描述

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

我們接著分析代碼邏輯,充錢(qián)線(xiàn)程之所以不再打印了是因?yàn)槌隽顺溴X(qián)最大限制while (calculate < MAX),那么理論上來(lái)說(shuō)deposit現(xiàn)在已經(jīng)是500了。

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

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

如何修改程序呢,其實(shí)也非常簡(jiǎn)單,只要在初始余額變量上加上volatile關(guān)鍵字即可。

static volatile int deposit = 0;       //初始余額,加上關(guān)鍵字

重新運(yùn)行輸出結(jié)果,這次不僅程序運(yùn)行符合預(yù)期,而且程序順利執(zhí)行完畢。每次充錢(qián)的線(xiàn)程執(zhí)行完畢,查詢(xún)余額的線(xiàn)程立刻感知到并且執(zhí)行了相應(yīng)的邏輯,整個(gè)程序邏輯上執(zhí)行順利,結(jié)果符合預(yù)期。

在這里插入圖片描述

問(wèn)題分析

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

在這里插入圖片描述

在這種結(jié)構(gòu)中,主存中的數(shù)據(jù)每個(gè)線(xiàn)程都可以訪(fǎng)問(wèn),但是本地工作空間只有本地線(xiàn)程可以訪(fǎng)問(wèn)。

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

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

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

volatile的原理

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

所以volatile關(guān)鍵字應(yīng)該具有以下用作:

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

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

這個(gè)協(xié)議會(huì)使得:

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

寫(xiě)操作,當(dāng)CPU將要修改某個(gè)共享變量的時(shí)候,CPU會(huì)發(fā)出信號(hào),通知其他的CPU將該變量在其他中緩存中副本所對(duì)應(yīng)的cache line置為無(wú)效,這也就導(dǎo)致了其他CPU的緩存中該變量失效,只能再次從主存中讀取。

在這里插入圖片描述

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

保證線(xiàn)程的有序性

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

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

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

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

volatile的應(yīng)用

說(shuō)了半天volatile關(guān)鍵字的原理,這里列舉幾個(gè)常用的場(chǎng)景。

Flag標(biāo)志:作為控制某個(gè)功能或者分支的flag標(biāo)志使用。

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

volatile與synchronized的區(qū)別

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

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

總結(jié)

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

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

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

相關(guān)文章

  • 一文掌握J(rèn)avaWeb登錄認(rèn)證

    一文掌握J(rèn)avaWeb登錄認(rèn)證

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

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

    本系統(tǒng)主要論述了如何使用JAVA語(yǔ)言開(kāi)發(fā)一個(gè)大學(xué)生心理服務(wù)系統(tǒng) ,本系統(tǒng)將嚴(yán)格按照軟件開(kāi)發(fā)流程進(jìn)行各個(gè)階段的工作,采用B/S架構(gòu),面向?qū)ο缶幊趟枷脒M(jìn)行項(xiàng)目開(kāi)發(fā)
    2022-09-09
  • SpringBoot實(shí)現(xiàn)對(duì)超大文件進(jìn)行異步壓縮下載的使用示例

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

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

    Netty分布式ByteBuf中PooledByteBufAllocator剖析

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

    MyBatisPlus 一對(duì)多、多對(duì)一、多對(duì)多的完美解決方案

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

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

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

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

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

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

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

    Java使用CountDownLatch實(shí)現(xiàn)網(wǎng)絡(luò)同步請(qǐng)求的示例代碼

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

    idea左側(cè)的commit框設(shè)置顯示出來(lái)方式

    在IDEA中顯示左側(cè)的commit框,首先通過(guò)File-Settings-Version Control-Commit進(jìn)行設(shè)置,然后勾選Use non-modal commit interface完成
    2025-01-01

最新評(píng)論