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

Java中的volatile關(guān)鍵字原理深入解析

 更新時(shí)間:2023年12月07日 10:24:33   作者:外星喵  
這篇文章主要介紹了Java中的volatile關(guān)鍵字原理深入解析,volatile是Java 編程語(yǔ)言允許線程訪問(wèn)共享變量,為了確保共享變量能被準(zhǔn)確和一致地更新,線程應(yīng)該確保通過(guò)排他鎖單獨(dú)獲得這個(gè)變量,需要的朋友可以參考下

volatile介紹

Java 語(yǔ)言規(guī)范 volatile 關(guān)鍵字定義:Java 編程語(yǔ)言允許線程訪問(wèn)共享變量,為了確保共享變量能被準(zhǔn)確和一致地更新,線程應(yīng)該確保通過(guò)排他鎖單獨(dú)獲得這個(gè)變量。

volatile通常被比喻成"輕量級(jí)的synchronized",也是Java并發(fā)編程中比較重要的一個(gè)關(guān)鍵字。

和synchronized不同,volatile是一個(gè)變量修飾符,只能用來(lái)修飾變量。無(wú)法修飾方法及代碼塊等。

volatile的用法比較簡(jiǎn)單,只需要在聲明一個(gè)可能被多線程同時(shí)訪問(wèn)的變量時(shí),使用volatile修飾就可以了。

private volatile static Singleton singleton;  

volatile的原理

每個(gè)線程有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程所使用到的變量(這些變量是從主內(nèi)存中拷貝而來(lái))。線程對(duì)變量的所有操作(讀取,賦值,抹除)都必須在工作內(nèi)存中進(jìn)行。不同線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成。

對(duì)于volatile變量,當(dāng)對(duì)volatile變量進(jìn)行寫操作的時(shí)候,JVM會(huì)向處理器發(fā)送一條lock前綴的指令,將這個(gè)緩存中的變量回寫到主存中。

但是就算寫回到主存,如果其他處理器緩存的值還是舊的,再執(zhí)行計(jì)算操作就會(huì)有問(wèn)題,所以在多處理器下,為了保證各個(gè)處理器的緩存是一致的,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議。

緩存一致性協(xié)議:每個(gè)處理器通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存里把數(shù)據(jù)讀到處理器緩存里。

所以,如果一個(gè)變量被volatile所修飾的話,在每次數(shù)據(jù)變化之后,其值都會(huì)被強(qiáng)制刷入主存。而其他處理器的緩存由于遵守了緩存一致性協(xié)議,也會(huì)把這個(gè)變量的值從主存加載到自己的緩存中。這就保證了一個(gè)volatile在并發(fā)編程中,其值在多個(gè)緩存中是可見的。

volatile與可見性

可見性是指當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。

Java內(nèi)存模型規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程中使用到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存。不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進(jìn)行數(shù)據(jù)同步進(jìn)行。所以,就可能出現(xiàn)線程1改了某個(gè)變量的值,但是線程2不可見的情況。

前面的關(guān)于volatile的原理中介紹過(guò)了,Java中的volatile關(guān)鍵字提供了一個(gè)功能,那就是被其修飾的變量在被修改后可以立即同步到主內(nèi)存,被其修飾的變量在每次使用之前都從主內(nèi)存刷新。因此,可以使用volatile來(lái)保證多線程操作時(shí)變量的可見性。

JVM對(duì) volatile 的描述如下描述中寫著,如果字節(jié)碼中有用VOLATILE修飾的,代表這個(gè)變量不能被線程所緩存。

可以通過(guò)一下代碼進(jìn)行驗(yàn)證,你會(huì)發(fā)現(xiàn)該程序不會(huì)結(jié)束,但是如果你對(duì)flag加上了volatile修飾的話,結(jié)果就會(huì)不一樣了:

    private static boolean flag = false;
    public static void main(String[] args) {
        new Thread(() -> {
        	flag = true;
    	}).start();
        while (!aBoolean) {
        }
        System.out.println("end");
    }
    private static void run() 

volatile與有序性

有序性即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。

普通的變量?jī)H僅會(huì)保證在該方法的執(zhí)行過(guò)程中所依賴的賦值結(jié)果的地方都能獲得正確的結(jié)果,而不能保證變量的賦值操作的順序與程序代碼中的執(zhí)行順序一致。由于處理器優(yōu)化和指令重排等,CPU還可能對(duì)輸入代碼進(jìn)行亂序執(zhí)行,比如load->add->save 有可能被優(yōu)化成load->save->add ,這就是有序性問(wèn)題。

volatile可以禁止指令重排,這就保證了代碼的程序會(huì)嚴(yán)格按照代碼的先后順序執(zhí)行。這就保證了有序性。被volatile修飾的變量的操作,會(huì)嚴(yán)格按照代碼順序執(zhí)行,load->add->save 的執(zhí)行順序就是:load->add->save 。

重排序

為什么要有重排序呢?

簡(jiǎn)單來(lái)說(shuō),就是為了提升執(zhí)行效率。

為什么能提升執(zhí)行效率呢?重排序可以提高程序的運(yùn)行效率,但是必須遵循 as-if-serial 語(yǔ)義。as-if-serial 語(yǔ)義是什么呢?簡(jiǎn)單來(lái)說(shuō),就是不管你怎么重排序,你必須保證不管怎么重排序,單線程下程序的執(zhí)行結(jié)果不能被改變(至于多線程就管不到了)。

內(nèi)存屏障

如何保證CPU不會(huì)對(duì)這些操作進(jìn)行重排序呢?

JVM是通過(guò)插入內(nèi)存屏障保證的,JMM 規(guī)范中定義的內(nèi)存屏障分為讀(load)屏障和寫(Store)屏障,排列組合就有了四種屏障。對(duì)于 volatile 操作,JMM 內(nèi)存屏障插入策略:

  • 在每個(gè) volatile 寫操作的前面插入一個(gè) StoreStore 屏障
  • 在每個(gè) volatile 寫操作的后面插入一個(gè) StoreLoad 屏障
  • 在每個(gè) volatile 讀操作的后面插入一個(gè) LoadLoad 屏障
  • 在每個(gè) volatile 讀操作的后面插入一個(gè) LoadStore 屏障

而在 x86 處理器中,有三種方法可以實(shí)現(xiàn)實(shí)現(xiàn) StoreLoad 屏障的效果,分別為:

  • mfence 指令:能實(shí)現(xiàn)全能型屏障,具備 lfence 和 sfence 的能力。
  • cpuid 指令:cpuid 操作碼是一個(gè)面向 x86 架構(gòu)的處理器補(bǔ)充指令,它的名稱派生自 CPU 識(shí)別,作用是允許軟件發(fā)現(xiàn)處理器的詳細(xì)信息。
  • lock 指令前綴:總線鎖。lock 前綴只能加在一些特殊的指令前面。

lock指令

實(shí)際上 HotSpot 關(guān)于 volatile 的實(shí)現(xiàn)就是使用的 lock 指令,只在 volatile 標(biāo)記的地方加上帶 lock 前綴指令操作,并沒有參照 JMM 規(guī)范的屏障設(shè)計(jì)。

lock 前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄),并提供以下三個(gè)功能:

  • 它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成;
  • 它會(huì)強(qiáng)制將對(duì)緩存的修改操作立即寫入主存;
  • 如果是寫操作,它會(huì)導(dǎo)致其他 CPU 中對(duì)應(yīng)的緩存行無(wú)效。

volatile與原子性

原子性是指一個(gè)操作是不可中斷的,要全部執(zhí)行完成,要不就都不執(zhí)行。

線程是CPU調(diào)度的基本單位。CPU有時(shí)間片的概念,會(huì)根據(jù)不同的調(diào)度算法進(jìn)行線程調(diào)度。當(dāng)一個(gè)線程獲得時(shí)間片之后開始執(zhí)行,在時(shí)間片耗盡之后,就會(huì)失去CPU使用權(quán)。所以在多線程場(chǎng)景下,由于時(shí)間片在線程間輪換,就會(huì)發(fā)生原子性問(wèn)題。

為了保證原子性,需要通過(guò)字節(jié)碼指令monitorenter和monitorexit,但是volatile和這兩個(gè)指令之間是沒有任何關(guān)系的。所以,volatile是不能保證原子性的。

在以下兩個(gè)場(chǎng)景中可以使用volatile來(lái)代替synchronized:

  • 運(yùn)算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程會(huì)修改變量的值。
  • 變量不需要與其他狀態(tài)變量共同參與不變約束。

除以上場(chǎng)景外,都需要使用其他方式來(lái)保證原子性,如synchronized或者JUC。

我們來(lái)看一下volatile和原子性的例子:

public class Test {
    public volatile int inc = 0;
    public void increase() {
        inc++;
    }
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
        while(Thread.activeCount()>1)  //保證前面的線程都執(zhí)行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

以上代碼比較簡(jiǎn)單,就是創(chuàng)建10個(gè)線程,然后分別執(zhí)行1000次j++操作。正常情況下,程序的輸出結(jié)果應(yīng)該是10000,但是,多次執(zhí)行的結(jié)果都小于10000。這其實(shí)就是volatile無(wú)法滿足原子性的原因。

對(duì)于一個(gè)簡(jiǎn)單的i++操作,一共有三個(gè)步驟:load , add ,save 。共享變量就會(huì)被多個(gè)線程同時(shí)進(jìn)行操作,這樣讀改寫操作就不是原子的,操作完之后共享變量的值可以會(huì)和期望的不一致。

DCL與volatile

為什么雙重校驗(yàn)鎖實(shí)現(xiàn)的單例中,已經(jīng)使用了synchronized,為什么還需要volatile?

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    	if (singleton == null) {  
       	 synchronized (Singleton.class) {  
       		 if (singleton == null) {  
          	  singleton = new Singleton();  
        	}  
        }  
      }  
      return singleton;  
    }  
}  

因?yàn)閟ingleton = new Singleton()不是一個(gè)原子操作,大概要經(jīng)過(guò)這幾個(gè)步驟:

  • 分配一塊內(nèi)存空間
  • 調(diào)用構(gòu)造器,初始化實(shí)例
  • singleton指向分配的內(nèi)存空間

由于cpu重排序問(wèn)題,導(dǎo)致實(shí)際執(zhí)行步驟可能是這樣的:

  • 申請(qǐng)一塊內(nèi)存空間
  • singleton指向分配的內(nèi)存空間
  • 調(diào)用構(gòu)造器,初始化實(shí)例

在singleton指向分配的內(nèi)存空間之后,singleton就不為空了。

但是在沒有調(diào)用構(gòu)造器初始化實(shí)例之前,這個(gè)對(duì)象還處于半初始化狀態(tài),在這個(gè)狀態(tài)下,實(shí)例的屬性都還是默認(rèn)屬性,這個(gè)時(shí)候如果有另一個(gè)線程調(diào)用getSingleton()方法時(shí),會(huì)拿到這個(gè)未初始化的對(duì)象,導(dǎo)致出錯(cuò)。 而加 volatile 修飾之后,就會(huì)禁止重排序,這樣就能保證在對(duì)象初始化完了之后才把singleton指向分配的內(nèi)存空間,杜絕了一些不可控錯(cuò)誤的產(chǎn)生。volatile 提供了 happens-before 保證,Happens-before 主要是解決前一個(gè)操作的結(jié)果必須對(duì)后一個(gè)操作可見。

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

相關(guān)文章

  • Java中簡(jiǎn)單實(shí)用Quartz概述

    Java中簡(jiǎn)單實(shí)用Quartz概述

    Quartz是一個(gè)開源的Java調(diào)度框架,可以用來(lái)實(shí)現(xiàn)在指定的時(shí)間或時(shí)間間隔觸發(fā)任務(wù)執(zhí)行的功能,這篇文章主要介紹了Java中簡(jiǎn)單實(shí)用Quartz,需要的朋友可以參考下
    2023-02-02
  • Java實(shí)現(xiàn)調(diào)用外部程序的示例代碼

    Java實(shí)現(xiàn)調(diào)用外部程序的示例代碼

    本文主要介紹了Java實(shí)現(xiàn)調(diào)用外部程序的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • Java實(shí)現(xiàn)抽獎(jiǎng)功能

    Java實(shí)現(xiàn)抽獎(jiǎng)功能

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • idea?intellij快速修復(fù)if語(yǔ)句缺少大括號(hào)的問(wèn)題

    idea?intellij快速修復(fù)if語(yǔ)句缺少大括號(hào)的問(wèn)題

    這篇文章主要介紹了idea?intellij快速修復(fù)if語(yǔ)句缺少大括號(hào)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Java 數(shù)組元素倒序的三種方式(小結(jié))

    Java 數(shù)組元素倒序的三種方式(小結(jié))

    這篇文章主要介紹了Java 數(shù)組元素倒序的三種方式(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • springboot集成WebSockets廣播消息(推薦)

    springboot集成WebSockets廣播消息(推薦)

    這篇文章主要介紹了springboot-集成WebSockets廣播消息,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Java線程之間通信的幾種方式詳解

    Java線程之間通信的幾種方式詳解

    在 Java 中,線程之間的通信是通過(guò)共享內(nèi)存模型來(lái)實(shí)現(xiàn)的,線程通過(guò)共享的對(duì)象和變量來(lái)交換數(shù)據(jù),為了確保線程間通信的正確性,Java 提供了一系列機(jī)制來(lái)實(shí)現(xiàn)線程安全、同步和通信,以下是常用的幾種線程間通信的方式,以及它們的使用方法和場(chǎng)景,需要的朋友可以參考下
    2025-03-03
  • Java不定參數(shù)使用及一些注意情況

    Java不定參數(shù)使用及一些注意情況

    不定參數(shù)是一種特殊的參數(shù)類型,它允許方法接受可變數(shù)量的參數(shù),本文主要介紹了Java不定參數(shù)使用及一些注意情況,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-03-03
  • Spring和IDEA不推薦使用@Autowired?注解原因解析

    Spring和IDEA不推薦使用@Autowired?注解原因解析

    這篇文章主要為大家介紹了Spring和IDEA不推薦使用@Autowired?注解原因解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • 手把手教你SpringBoot輕松整合Minio

    手把手教你SpringBoot輕松整合Minio

    這篇文章主要介紹了手把手教你SpringBoot輕松整合Minio的方法,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2021-01-01

最新評(píng)論