欧美大屁股bbbbxxxx,狼人大香伊蕉国产www亚洲,男ji大巴进入女人的视频小说,男人把ji大巴放进女人免费视频,免费情侣作爱视频

歡迎來到入門教程網(wǎng)!

Java

當(dāng)前位置:主頁 > 軟件編程 > Java >

Java內(nèi)存模型可見性問題相關(guān)解析

來源:本站原創(chuàng)|時(shí)間:2020-01-10|欄目:Java|點(diǎn)擊: 次

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

前言

之前的文章中講到,JMM是內(nèi)存模型規(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();
  }

}

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

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)設(shè)置成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)象可能會(huì)讓人比較困惑,為什么有時(shí)候check-Thread線程能感知到狀態(tài)的變化,有時(shí)候又感知不到變化呢?這個(gè)現(xiàn)象就是在多核CPU多線程編程環(huán)境下會(huì)出現(xiàn)的可見性問題。

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

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

JMM對可見性問題的保證

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

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

volatile關(guān)鍵字

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

private volatile boolean started = false;

使用volatile修飾一個(gè)共享變量可以達(dá)到如下的效果:

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

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

那么volatile具體是怎么達(dá)到上面兩個(gè)效果的呢?其實(shí)volatile底層使用的是內(nèi)存屏障來保證可見性的。

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

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

對內(nèi)存屏障做下簡單的總結(jié):

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

synchronized關(guān)鍵字

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

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());
  }
}

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

Lock接口

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

CAS機(jī)制(Atomic類)

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

private AtomicBoolean started = new AtomicBoolean(false);

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

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。

上一篇:SpringCloud融入Python的實(shí)現(xiàn)

欄    目:Java

下一篇:springboot2.0以上調(diào)度器配置線程池的實(shí)現(xiàn)

本文標(biāo)題:Java內(nèi)存模型可見性問題相關(guān)解析

本文地址:http://mengdiqiu.com.cn/a1/Java/8871.html

網(wǎng)頁制作CMS教程網(wǎng)絡(luò)編程軟件編程腳本語言數(shù)據(jù)庫服務(wù)器

如果侵犯了您的權(quán)利,請與我們聯(lián)系,我們將在24小時(shí)內(nèi)進(jìn)行處理、任何非本站因素導(dǎo)致的法律后果,本站均不負(fù)任何責(zé)任。

聯(lián)系QQ:835971066 | 郵箱:835971066#qq.com(#換成@)

Copyright © 2002-2020 腳本教程網(wǎng) 版權(quán)所有