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

Java多線程之volatile關(guān)鍵字及內(nèi)存屏障實(shí)例解析

 更新時間:2019年05月31日 14:07:03   作者:老胡  
volatile是JVM提供的一種最輕量級的同步機(jī)制,因?yàn)镴ava內(nèi)存模型為volatile定義特殊的訪問規(guī)則,使其可以實(shí)現(xiàn)Java內(nèi)存模型中的兩大特性:可見性和有序性。這篇文章主要介紹了Java多線程之volatile關(guān)鍵字及內(nèi)存屏障,需要的朋友可以參考下

前面一篇文章在介紹Java內(nèi)存模型的三大特性(原子性、可見性、有序性)時,在可見性和有序性中都提到了volatile關(guān)鍵字,那這篇文章就來介紹volatile關(guān)鍵字的內(nèi)存語義以及實(shí)現(xiàn)其特性的內(nèi)存屏障。

volatile是JVM提供的一種最輕量級的同步機(jī)制,因?yàn)镴ava內(nèi)存模型為volatile定義特殊的訪問規(guī)則,使其可以實(shí)現(xiàn)Java內(nèi)存模型中的兩大特性:可見性和有序性。正因?yàn)関olatile關(guān)鍵字具有這兩大特性,所以我們可以使用volatile關(guān)鍵字解決多線程中的某些同步問題。

volatile的可見性

volatile的可見性是指當(dāng)一個變量被volatile修飾后,這個變量就對所有線程均可見。白話點(diǎn)就是說當(dāng)一個線程修改了一個volatile修飾的變量后,其他線程可以立刻得知這個變量的修改,拿到最這個變量最新的值。

結(jié)合前一篇文章提到的Java內(nèi)存模型中線程、工作內(nèi)存、主內(nèi)存的交互關(guān)系,我們對volatile的可見性也可以這么理解,定義為volatile修飾的變量,在線程對其進(jìn)行寫入操作時不會把值緩存在工作內(nèi)存中,而是直接把修改后的值刷新回寫到主內(nèi)存,而當(dāng)處理器監(jiān)控到其他線程中該變量在主內(nèi)存中的內(nèi)存地址發(fā)生變化時,會讓這些線程重新到主內(nèi)存中拷貝這個變量的最新值到工作內(nèi)存中,而不是繼續(xù)使用工作內(nèi)存中舊的緩存。

下面我列舉一個利用volatile可見性解決多線程并發(fā)安全的示例:

public class VolatileDemo {
 //private static boolean isReady = false;
 private static volatile boolean isReady = false;
 static class ReadyThread extends Thread {
  public void run() {
   while (!isReady) {    
   }
   System.out.println("ReadyThread finish");
  }
 }
 public static void main(String[] args) throws InterruptedException {
  new ReadyThread().start();
  Thread.sleep(1000);//sleep 1秒鐘確保ReadyThread線程已經(jīng)開始執(zhí)行
  isReady = true;
 }
}

上面這段代碼運(yùn)行之后最終會在控制臺打印出: ReadyThread finish ,而當(dāng)你將變量isReady的volatile修飾符去掉之后再運(yùn)行則會發(fā)現(xiàn)程序一直運(yùn)行而不結(jié)束,而控制臺也沒有任何打印輸出。

我們分析下這個程序:初始時isReady為false,所以ReadyThread線程啟動開始執(zhí)行后,它的while代碼塊因標(biāo)志位isReady為false會進(jìn)入死循環(huán),當(dāng)用volatile關(guān)鍵字修飾isReady時,main方法所在的線程將isReady修改為true之后,ReadyThread線程會立刻得知并獲取這個最新的isReady值,緊接著while循環(huán)就會結(jié)束循環(huán),所以最后打印出了相關(guān)文字。而當(dāng)未用volatile修飾時,main方法所在的線程雖然修改了isReady變量,但ReadyThread線程并不知道這個修改,所以使用的還是之前的舊值,因此會一直死循環(huán)執(zhí)行while語句。

volatile的有序性

有序性是指程序代碼的執(zhí)行是按照代碼的實(shí)現(xiàn)順序來按序執(zhí)行的。

volatile的有序性特性則是指禁止JVM指令重排優(yōu)化。

我們來看一個例子:

public class Singleton {
 private static Singleton instance = null;
 //private static volatile Singleton instance = null;
 private Singleton() { }

 public static Singleton getInstance() {
  //第一次判斷
  if(instance == null) {
   synchronized (Singleton.class) {
    if(instance == null) {
     //初始化,并非原子操作
     instance = new Singleton(); 
    }
   }
  }
  return instance;
 }
}

上面的代碼是一個很常見的單例模式實(shí)現(xiàn)方式,但是上述代碼在多線程環(huán)境下是有問題的。為什么呢,問題出在instance對象的初始化上,因?yàn)?code> instance = new Singleton(); 這個初始化操作并不是原子的,在JVM上會對應(yīng)下面的幾條指令:

memory =allocate(); //1. 分配對象的內(nèi)存空間 
ctorInstance(memory); //2. 初始化對象 
instance =memory;  //3. 設(shè)置instance指向剛分配的內(nèi)存地址

上面三個指令中,步驟2依賴步驟1,但是步驟3不依賴步驟2,所以JVM可能針對他們進(jìn)行指令重拍序優(yōu)化,重排后的指令如下:

memory =allocate(); //1. 分配對象的內(nèi)存空間 
instance =memory;  //3. 設(shè)置instance指向剛分配的內(nèi)存地址
ctorInstance(memory); //2. 初始化對象

這樣優(yōu)化之后,內(nèi)存的初始化被放到了instance分配內(nèi)存地址的后面,這樣的話當(dāng)線程1執(zhí)行步驟3這段賦值指令后,剛好有另外一個線程2進(jìn)入getInstance方法判斷instance不為null,這個時候線程2拿到的instance對應(yīng)的內(nèi)存其實(shí)還未初始化,這個時候拿去使用就會導(dǎo)致出錯。

所以我們在用這種方式實(shí)現(xiàn)單例模式時,會使用volatile關(guān)鍵字修飾instance變量,這是因?yàn)関olatile關(guān)鍵字除了可以保證變量可見性之外,還具有防止指令重排序的作用。當(dāng)用volatile修飾instance之后,JVM執(zhí)行時就不會對上面提到的初始化指令進(jìn)行重排序優(yōu)化,這樣也就不會出現(xiàn)多線程安全問題了。

volatile使用場景

volatile的可以在以下場景中使用:

當(dāng)運(yùn)算結(jié)果不依賴變量當(dāng)前的值,或者能確保只有單一線程修改變量的值的時候,我們才可以對該變量使用volatile關(guān)鍵字
變量不需要與其他狀態(tài)變量共同參與不變約束

volatile與原子性

volatile關(guān)鍵字能保證變量的可見性和代碼的有序性,但是不能保證變量的原子性,下面我再舉一個volatile與原子性的例子:

public class VolatileTest {
 public static volatile int count = 0;

 public static void increase() {
  count++;
 }

 public static void main(String[] args) {
  Thread[] threads = new Thread[20];
  for(int i = 0; i < threads.length; i++) {
   threads[i] = new Thread(() -> {
    for(int j = 0; j < 1000; j++) {
     increase();
    }
   });
   threads[i].start();
  }
  //等待所有累加線程結(jié)束
  while (Thread.activeCount() > 1) {
   Thread.yield();
  }
  System.out.println(count);
 }
}

上面這段代碼創(chuàng)建了20個線程,每個線程對變量count進(jìn)行1000次自增操作,如果這段代碼并發(fā)正常的話,結(jié)果應(yīng)該是20000,但實(shí)際運(yùn)行過程中經(jīng)常會出現(xiàn)小于20000的結(jié)果,因?yàn)閏ount++這個自增操作不是原子操作。

上面的count++自增操作等價于count=count+1,所以JVM需要先讀取count的值,然后在count的基礎(chǔ)上給它加1,然后再將新的值重新賦值給count變量,所以這個自增總共需要三步。

 

上圖中我將線程對count的自增操作畫了個簡單的流程,一個線程要對count進(jìn)行自增時要先讀取count的值,然后在當(dāng)前count值的基礎(chǔ)上進(jìn)行count+1操作,最后將count的新值重新寫回到count。

如果線程2在線程1讀取count舊值寫回count新值期間讀取count的值,顯然這個時候線程2讀取的是count還未更新的舊值,這時兩個線程是對同一個值進(jìn)行了+1操作,這樣這兩個線程就沒有對count實(shí)現(xiàn)累加效果,相反這些操作卻又沒有違反volatile的定義,所以這種情況下使用volatile依然會存在多線程并發(fā)安全的問題。

volatile與內(nèi)存屏障

前面介紹了volatile的可見性和有序性,那JVM到底是如何為volatile關(guān)鍵字實(shí)現(xiàn)的這兩大特性呢,Java內(nèi)存模型其實(shí)是通過內(nèi)存屏障(Memory Barrier)來實(shí)現(xiàn)的。

內(nèi)存屏障其實(shí)也是一種JVM指令,Java內(nèi)存模型的重排規(guī)則會要求Java編譯器在生成JVM指令時插入特定的內(nèi)存屏障指令,通過這些內(nèi)存屏障指令來禁止特定的指令重排序。

另外內(nèi)存屏障還具有一定的語義:內(nèi)存屏障之前的所有寫操作都要回寫到主內(nèi)存,內(nèi)存屏障之后的所有讀操作都能獲得內(nèi)存屏障之前的所有寫操作的最新結(jié)果(實(shí)現(xiàn)了可見性)。因此重排序時,不允許把內(nèi)存屏障之后的指令重排序到內(nèi)存屏障之前。

下面的表是volatile有關(guān)的禁止指令重排的行為:

第一個操作 第二個操作:普通讀寫 第二個操作:volatile讀 第二個操作:volatile寫
普通讀寫 可以重排 可以重排 不可以重排
volatile讀 不可以重排 不可以重排 不可以重排
volatile寫 可以重排 不可以重排 不可以重排

從上面的表我們可以得出下面這些結(jié)論:

當(dāng)?shù)诙€操作volatile寫時,不論第一個操作是什么,都不能重排序。這個規(guī)則保證了volatile寫之前的操作不會被重排到volatile寫之后。

當(dāng)?shù)谝粋€操作為volatile讀時,不論第二個操作是什么,都不能重排。這個操作保證了volatile讀之后的操作不會被重排到volatile讀之前。

當(dāng)?shù)谝粋€操作為volatile寫,第二個操作為volatile讀時,不能重排。

JVM中提供了四類內(nèi)存屏障指令:

屏障類型 指令示例 說明
LoadLoad Load1; LoadLoad; Load2 保證load1的讀取操作在load2及后續(xù)讀取操作之前執(zhí)行
StoreStore Store1; StoreStore; Store2 在store2及其后的寫操作執(zhí)行前,保證store1的寫操作已刷新到主內(nèi)存
LoadStore Load1; LoadStore; Store2 在stroe2及其后的寫操作執(zhí)行前,保證load1的讀操作已讀取結(jié)束
StoreLoad Store1; StoreLoad; Load2 保證store1的寫操作已刷新到主內(nèi)存之后,load2及其后的讀操作才能執(zhí)行

總結(jié)

volatile實(shí)現(xiàn)了Java內(nèi)存模型中的可見性和有序性,它的這兩大特性則是通過內(nèi)存屏障來實(shí)現(xiàn)的,同時volatile無法保證原子性。

以上所述是小編給大家介紹的Java多線程之volatile關(guān)鍵字及內(nèi)存屏障實(shí)例解析,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!

相關(guān)文章

  • Java使用Redis實(shí)現(xiàn)微博熱搜功能

    Java使用Redis實(shí)現(xiàn)微博熱搜功能

    在社交平臺上,熱搜功能是一個非常重要的組成部分,它展示了當(dāng)前最熱門的話題,幫助用戶迅速了解最受關(guān)注的事件,Redis 是一個高性能的鍵值存儲系統(tǒng),通常用于緩存和實(shí)時數(shù)據(jù)存儲,本文將通過 Java 結(jié)合 Redis 實(shí)現(xiàn)一個簡化版的微博熱搜功能,需要的朋友可以參考下
    2024-12-12
  • 詳解SpringBoot中@PostMapping注解的用法

    詳解SpringBoot中@PostMapping注解的用法

    在SpringBoot中,我們經(jīng)常需要編寫RESTful Web服務(wù),以便于客戶端與服務(wù)器之間的通信,@PostMapping注解可以讓我們更方便地編寫POST請求處理方法,在本文中,我們將介紹@PostMapping注解的作用、原理,以及如何在SpringBoot應(yīng)用程序中使用它
    2023-06-06
  • Spring Boot中RedisTemplate的使用示例詳解

    Spring Boot中RedisTemplate的使用示例詳解

    RedisTemplate.opsForHash()是RedisTemplate類提供的用于操作Hash類型的方法,它可以用于對Redis中的Hash數(shù)據(jù)結(jié)構(gòu)進(jìn)行各種操作,如設(shè)置字段值、獲取字段值、刪除字段值等,本文介紹Spring Boot中RedisTemplate的使用,感興趣的朋友一起看看吧
    2023-10-10
  • SpringMVC項(xiàng)目異常處理機(jī)制詳解

    SpringMVC項(xiàng)目異常處理機(jī)制詳解

    SpringMVC是一種基于Java,實(shí)現(xiàn)了Web MVC設(shè)計模式,請求驅(qū)動類型的輕量級Web框架,即使用了MVC架構(gòu)模式的思想,將Web層進(jìn)行職責(zé)解耦?;谡埱篁?qū)動指的就是使用請求-響應(yīng)模型,框架的目的就是幫助我們簡化開發(fā),SpringMVC也是要簡化我們?nèi)粘eb開發(fā)
    2022-08-08
  • Idea跑的項(xiàng)目沒問題將程序install成jar包運(yùn)行報錯空指針的問題

    Idea跑的項(xiàng)目沒問題將程序install成jar包運(yùn)行報錯空指針的問題

    這篇文章主要介紹了Idea跑的項(xiàng)目沒問題,將程序install成jar包運(yùn)行報錯空指針的問題,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • Java圖像處理工具類

    Java圖像處理工具類

    這里給大家分享了一個java常用的圖像處理工具類,包含縮放圖像、切割圖像、圖像類型轉(zhuǎn)換、彩色轉(zhuǎn)黑白、文字水印、圖片水印等,有需要的小伙伴參考下。
    2015-02-02
  • Springboot中的@Order如何使用

    Springboot中的@Order如何使用

    本文主要介紹了Springboot中的@Order如何使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • Java中用戶向系統(tǒng)傳遞參數(shù)的三種基本方式實(shí)例分享

    Java中用戶向系統(tǒng)傳遞參數(shù)的三種基本方式實(shí)例分享

    這篇文章主要介紹了Java中用戶向系統(tǒng)傳遞參數(shù)的三種基本方式實(shí)例,有需要的朋友可以參考一下
    2014-01-01
  • 教你開發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細(xì)過程

    教你開發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細(xì)過程

    這篇文章主要介紹了開發(fā)腳手架集成Spring?Boot?Actuator監(jiān)控的詳細(xì)過程,集成包括引入依賴配置文件及訪問驗(yàn)證的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • Java計時器工具StopWatch的具體使用

    Java計時器工具StopWatch的具體使用

    計時器在很多地方都可以用到,本文主要介紹了Java計時器工具StopWatch的具體使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04

最新評論