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

Java內存模型可見性問題相關解析

 更新時間:2019年12月25日 15:10:04   作者:寫代碼的木公  
這篇文章主要介紹了Java內存模型可見性問題相關解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

這篇文章主要介紹了Java內存模型可見性問題相關解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

前言

之前的文章中講到,JMM是內存模型規(guī)范在Java語言中的體現(xiàn)。JMM保證了在多核CPU多線程編程環(huán)境下,對共享變量讀寫的原子性、可見性和有序性。

本文就具體來講講JMM是如何保證共享變量訪問的可見性的。

什么是可見性問題

我們從一段簡單的代碼來看看到底什么是可見性問題。

public class VolatileDemo {

  boolean started = false;

  public void startSystem(){
    System.out.println(Thread.currentThread().getName()+" begin to start system, time:"+System.currentTimeMillis());
    started = true;
    System.out.println(Thread.currentThread().getName()+" success to start system, time:"+System.currentTimeMillis());
  }

  public void checkStartes(){
    if (started){
      System.out.println("system is running, time:"+System.currentTimeMillis());
    }else {
      System.out.println("system is not running, time:"+System.currentTimeMillis());
    }
  }

  public static void main(String[] args) {
    VolatileDemo demo = new VolatileDemo();
    Thread startThread = new Thread(new Runnable() {
      @Override
      public void run() {
        demo.startSystem();
      }
    });
    startThread.setName("start-Thread");

    Thread checkThread = new Thread(new Runnable() {
      @Override
      public void run() {
        while (true){
          demo.checkStartes();
        }
      }
    });
    checkThread.setName("check-Thread");
    startThread.start();
    checkThread.start();
  }

}

上面的列子中,一個線程來改變started的狀態(tài),另外一個線程不停地來檢測started的狀態(tài),如果是true就輸出系統(tǒng)啟動,如果是false就輸出系統(tǒng)未啟動。那么當start-Thread線程將狀態(tài)改成true后,check-Thread線程在執(zhí)行時是否能立即“看到”這個變化呢?答案是不一定能立即看到。這邊我做了很多測試,大多數(shù)情況下是能“感知”到started這個變量的變化的。但是偶爾會存在感知不到的情況。請看下下面日志記錄:

start-Thread begin to start system, time:1577079553515
start-Thread success to start system, time:1577079553516 
system is not running, time:1577079553516  ==>此處start-Thread線程已經(jīng)將狀態(tài)設置成true,但是check-Thread線程還是沒檢測到
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553516
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553517
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519
system is running, time:1577079553519

上面的現(xiàn)象可能會讓人比較困惑,為什么有時候check-Thread線程能感知到狀態(tài)的變化,有時候又感知不到變化呢?這個現(xiàn)象就是在多核CPU多線程編程環(huán)境下會出現(xiàn)的可見性問題。

Java內存模型規(guī)定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程在工作內存中保存的值是主內存中值的副本,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存。等到線程對變量操作完畢之后會將變量的最新值刷新回到主內存。

但是何時刷新這個最新值又是隨機的。所以就有可能一個線程已經(jīng)將一個共享變量更新了,但是還沒刷新回主內存,那么這時其他對這個變量進行讀寫的線程就看不到這個最新值。這個就是多CPU多線程編程環(huán)境下的可見性問題。也是上面代碼會出現(xiàn)問題的原因。

JMM對可見性問題的保證

在多CPU多線程編程環(huán)境下,對共享變量的讀寫會出現(xiàn)可見性問題。但是幸好JMM提供了相應的技術手段來幫我們規(guī)避這些問題,可以讓程序正確運行。JMM針對可見性問題,主要提供了如下手段:

  • volatile關鍵字
  • synchronized關鍵字
  • Lock鎖
  • CAS操作(原子操作類)

volatile關鍵字

使用volatile關鍵字修飾一個變量可以保證變量的可見性。所以對于上面的代碼,我們只需要簡單的修改下代碼就可以讓程序正確運行了。

private volatile boolean started = false;

使用volatile修飾一個共享變量可以達到如下的效果:

一旦線程對這個共享變量的副本做了修改,會立馬刷新最新值到主內存中去;

一旦線程對這個共享變量的副本做了修改,其他線程中對這個共享變量拷貝的副本值會失效,其他線程如果需要對這個共享變量進行讀寫,必須重新從主內存中加載。

那么volatile具體是怎么達到上面兩個效果的呢?其實volatile底層使用的是內存屏障來保證可見性的。

內存屏障(英語:Memory barrier),也稱內存柵欄,內存柵障,屏障指令等,是一類同步屏障指令,是CPU或編譯器在對內存隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執(zhí)行后才可以開始執(zhí)行此點之后的操作。大多數(shù)現(xiàn)代計算機為了提高性能而采取亂序執(zhí)行,這使得內存屏障成為必須。

語義上,內存屏障之前的所有寫操作都要寫入內存;內存屏障之后的讀操作都可以獲得同步屏障之前的寫操作的結果。因此,對于敏感的程序塊,寫操作之后、讀操作之前可以插入內存屏障。

對內存屏障做下簡單的總結:

  • 內存屏障是一個指令級別的同步點;
  • 內存屏障之前的寫操作都必須立馬刷新回主內存;
  • 內存屏障之后的讀操作都必須從主內存中讀取最新值;
  • 在有內存屏障的地方,會禁止指令重排序,即屏障下面的代碼不能跟屏障上面的代碼交換執(zhí)行順序,即在執(zhí)行到內存屏障這句指令時,在它前面的操作已經(jīng)全部完成。

synchronized關鍵字

使用synchronized代碼塊或者synchronized方法也可以保證共享變量的可見性。只要如下修改上面的代碼,我們就能得到正確的執(zhí)行結果。

public synchronized void startSystem(){
  System.out.println(Thread.currentThread().getName()+" begin to start system, time:"+System.currentTimeMillis());
  value = 2;
  started = true;
  System.out.println(Thread.currentThread().getName()+" success to start system, time:"+System.currentTimeMillis());
}

public synchronized void checkStartes(){
  if (started){
    System.out.println("system is running, time:"+System.currentTimeMillis());
  }else {
    System.out.println("system is not running, time:"+System.currentTimeMillis());
  }
}

當線程釋放鎖時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存中。當線程獲取鎖時,JMM會把該線程對應的本地內存置為無效。從而使得被監(jiān)視器保護的臨界區(qū)代碼必須從主內存中讀取共享變量。我們發(fā)現(xiàn)鎖具有和volatile一致的內存語義,所以使用synchronized也可以實現(xiàn)共享變量的可見性。

Lock接口

使用Lock相關的實現(xiàn)類也可以保證共享變量的可見性。其實現(xiàn)原理和synchronized的實現(xiàn)原理類似,這邊也就不再贅述了。

CAS機制(Atomic類)

使用原子操作類也可以保證共享變量操作的可見性。所以我們只要如下修稿上面的代碼就行了。

private AtomicBoolean started = new AtomicBoolean(false);

原子操作類底層使用的是CAS機制。Java中CAS機制每次都會從主內存中獲取最新值進行compare,比較一致之后才會將新值set到主內存中去。而且這個整個操作是一個原子操作。所以CAS操作每次拿到的都是主內存中的最新值,每次set的值也會立即寫到主內存中。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Kryo序列化及反序列化用法示例

    Kryo序列化及反序列化用法示例

    這篇文章主要介紹了Kryo序列化及反序列化用法示例,小編覺得挺不錯的,這里分享給大家,需要的朋友可以參考下。
    2017-10-10
  • Jmeter配置代理實現(xiàn)錄制過程圖解

    Jmeter配置代理實現(xiàn)錄制過程圖解

    這篇文章主要介紹了Jmeter配置代理實現(xiàn)錄制過程圖解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-08-08
  • 關于快速測試API接口的一個新技能

    關于快速測試API接口的一個新技能

    這篇文章主要給大家介紹了關于快速測試API接口的一個新技能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2018-06-06
  • 使用SpringBoot發(fā)送郵箱驗證碼的簡單實現(xiàn)

    使用SpringBoot發(fā)送郵箱驗證碼的簡單實現(xiàn)

    這篇文章主要介紹了使用SpringBoot發(fā)送郵箱驗證碼的簡單實現(xiàn),咱們今天來講使用QQ郵箱來發(fā)送和接收驗證碼,首先來介紹一下它在SpringBoot項目中的具體應用,需要的朋友可以參考下
    2023-04-04
  • java開發(fā)RocketMQ消息中間件原理基礎詳解

    java開發(fā)RocketMQ消息中間件原理基礎詳解

    最近 RocketMQ 剛剛上生產環(huán)境,閑暇之時在這里做一些分享,主要目的是讓初學者能快速上手RocketMQ,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • Java如何通過反射機制獲取數(shù)據(jù)類對象的屬性及方法

    Java如何通過反射機制獲取數(shù)據(jù)類對象的屬性及方法

    文章介紹了如何使用Java反射機制獲取類對象的所有屬性及其對應的get、set方法,以及如何通過反射機制實現(xiàn)類對象的實例化,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • java比較兩個list是否相同equals的代碼詳解

    java比較兩個list是否相同equals的代碼詳解

    在本篇文章里小編給大家分享的是關于java比較兩個list是否相同equals的代碼詳解,有需要的朋友們可以參考學習下。
    2020-02-02
  • 解決DataOutputStream亂碼的問題

    解決DataOutputStream亂碼的問題

    這篇文章主要介紹了DataOutputStream亂碼問題的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Java自動拆箱空指針異常的解決

    Java自動拆箱空指針異常的解決

    這篇文章主要介紹了Java自動拆箱空指針異常的解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • Spring注入Map集合實現(xiàn)策略模式詳解

    Spring注入Map集合實現(xiàn)策略模式詳解

    這篇文章主要介紹了Spring注入Map集合實現(xiàn)策略模式詳解,Spring提供通過@Resource注解將相同類型的對象注入到Map集合,并將對象的名字作為key,對象作為value封裝進入Map,需要的朋友可以參考下
    2023-11-11

最新評論

黄陵县| 凭祥市| 华阴市| 深泽县| 绥德县| 通山县| 玛沁县| 呼和浩特市| 柏乡县| 咸丰县| 南宁市| 元阳县| 定兴县| 饶河县| 水城县| 玛沁县| 武功县| 靖边县| 慈利县| 琼结县| 道孚县| 城市| 女性| 布尔津县| 双桥区| 舟曲县| 裕民县| 隆安县| 凤台县| 中西区| 连平县| 舒城县| 清河县| 彭山县| 永康市| 白银市| 墨脱县| 保康县| 柘城县| 华池县| 无为县|