激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - java 多線程與并發(fā)之volatile詳解分析

java 多線程與并發(fā)之volatile詳解分析

2022-03-09 00:33臨木小屋 Java教程

volatile這個關(guān)鍵字可能很多朋友都聽說過,或許也都用過。在Java 5之前,它是一個備受爭議的關(guān)鍵字,因為在程序中使用它往往會導(dǎo)致出人意料的結(jié)果。在Java 5之后,volatile關(guān)鍵字才得以重獲生機

CPU、內(nèi)存、緩存的關(guān)系

要理解JMM,要先從計算機底層開始,下面是一份大佬的研究報告

java 多線程與并發(fā)之volatile詳解分析

計算機在做一些我們平時的基本操作時,需要的響應(yīng)時間是不一樣的!如果我們計算一次a+b所需要的的時間:

  • CPU讀取內(nèi)存獲得a,100納秒
  • CPU讀取內(nèi)存獲得b,100納秒
  • CPU執(zhí)行一條指令 a+b ,0.6納秒

也就是說99%的時間花費在CPU讀取內(nèi)存上了,那如何解決速度不均衡問題?

早期計算機中cpu和內(nèi)存的速度是差不多的,但在現(xiàn)代計算機中cpu的指令速度遠超內(nèi)存的存取速度,由于計算機的存儲設(shè)備與處理器的運算速度有幾個數(shù)量級的差距,所以現(xiàn)代計算機系統(tǒng)都不得不加入一層讀寫速度盡可能接近處理器運算速度的高速緩存(Cache)來作為內(nèi)存與處理器之間的緩沖:將運算需要使用到的數(shù)據(jù)復(fù)制到緩存中,讓運算能快速進行,當(dāng)運算結(jié)束后再從緩存同步回內(nèi)存之中,這樣處理器就無須等待緩慢的內(nèi)存讀寫了

 

CPU緩存

什么是CPU緩存

在計算機系統(tǒng)中,CPU高速緩存(英語:CPU Cache,在本文中簡稱緩存)是用于減少處理器訪問內(nèi)存所需平均時間的部件。在金字塔式存儲體系中它位于自頂向下的第二層,僅次于CPU寄存器。其容量遠小于內(nèi)存,但速度卻可以接近處理器的頻率。當(dāng)處理器發(fā)出內(nèi)存訪問請求時,會先查看緩存內(nèi)是否有請求數(shù)據(jù)。如果存在(命中),則不經(jīng)訪問內(nèi)存直接返回該數(shù)據(jù);如果不存在(失效),則要先把內(nèi)存中的相應(yīng)數(shù)據(jù)載入緩存,再將其返回處理器。

下圖是一個典型的存儲器層次結(jié)構(gòu),我們可以看到一共使用了三級緩存:

java 多線程與并發(fā)之volatile詳解分析

為什么要有多級CPU Cache

在計算機系統(tǒng)中,寄存器劃是L0級緩存,接著依次是L1,L2,L3(接下來是內(nèi)存,本地磁盤,遠程存儲)。越往上的緩存存儲空間越小,速度越快,成本也更高;越往下的存儲空間越大,速度更慢,成本也更低。從上至下,每一層都可以看做是更下一層的緩存,即:L0寄存器是L1一級緩存的緩存,L1是L2的緩存,依次類推;每一層的數(shù)據(jù)都是來至它的下一層,所以每一層的數(shù)據(jù)是下一層的數(shù)據(jù)的子集

java 多線程與并發(fā)之volatile詳解分析


下圖是我電腦的三級緩存,可以看到層級越小容量越小。速度越快價格越高!!

java 多線程與并發(fā)之volatile詳解分析

在現(xiàn)代CPU上,一般來說L0, L1,L2,L3都集成在CPU內(nèi)部,而L1還分為一級數(shù)據(jù)緩存(Data Cache,D-Cache,L1d)和一級指令緩存(Instruction Cache,I-Cache,L1i),分別用于存放數(shù)據(jù)和執(zhí)行數(shù)據(jù)的指令解碼。每個核心擁有獨立的運算處理單元、控制器、寄存器、L1、L2緩存,然后一個CPU的多個核心共享最后一層CPU緩存L3。

為了充分利用 CPU Cache,Java提出了內(nèi)存模型這個概念

 

Java內(nèi)存模型(Java Memory Model,JMM)

從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲在主內(nèi)存(Main Memory)中,每個線程都有一個私有的本地內(nèi)存(Local Memory),本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存、寫緩沖區(qū)、寄存器以及其他的硬件和編譯器優(yōu)化。

java 多線程與并發(fā)之volatile詳解分析

程之間的共享變量存儲在主內(nèi)存(Main Memory)中,每個線程都有一個私有的工作內(nèi)存(Local Memory),工作內(nèi)存中存儲了該線程以讀/寫共享變量的副本。

java 多線程與并發(fā)之volatile詳解分析

舉個栗子:多個線程去修改主內(nèi)存中的變量a。線程不能直接修改主內(nèi)存中的數(shù)據(jù),先把數(shù)據(jù)拷貝到工作內(nèi)存,線程對私有的工作內(nèi)存修改然后再同步到主內(nèi)存。那這樣做會帶來什么問題呢?

 

JMM導(dǎo)致的并發(fā)安全問題

從JMM角度看,如果兩個線程同時調(diào)用 a=a+1這個函數(shù)(假設(shè)a的初始值是0),A、B線程同時從主內(nèi)存中拷貝a=0,然后修改寫回,最后主內(nèi)存為a=1,咋搞?

java 多線程與并發(fā)之volatile詳解分析

如下是代碼栗子

public class MainTest {
  
  private  long count = 0;

  public  void incCount() {
      count += 1;
  }

  public static void main(String[] args) throws InterruptedException {

      MainTest test = new MainTest();
      Count count = new Count(test);
      Count count1 = new Count(test);
      count.start();
      count1.start();
      Thread.sleep(5);
      System.out.println("result is :" + test.count);
  }
  
  private static class Count extends Thread{
      private MainTest m;

      public Count(MainTest m){
          this.m = m;
      }

      @Override
      public void run() {
          for (int i = 0; i < 10000; i++) {
              m.incCount();
          }
      }
  }
} 

執(zhí)行結(jié)果

// 第一次執(zhí)行
> Task :lib-test:MainTest.main()
result is :11861

// 第二次執(zhí)行
> Task :lib-test:MainTest.main()
result is :10535

 

可見性

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

由于線程對變量的所有操作都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存中的變量,那么對于共享變量a,它們首先是在自己的工作內(nèi)存,之后再同步到主內(nèi)存。可是并不會及時的刷到主存中,而是會有一定時間差。很明顯,這個時候線程 A 對變量 a 的操作對于線程 B 而言就不具備可見性了 。

要解決共享對象可見性這個問題,我們可以使用volatile關(guān)鍵字或者是加鎖

 

原子性

即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行

我們都知道CPU資源的分配都是以線程為單位的,并且是分時調(diào)用,操作系統(tǒng)允許某個進程執(zhí)行一小段時間,例如 50 毫秒,過了 50 毫秒操作系統(tǒng)就會重新選擇一個進程來執(zhí)行(我們稱為“任務(wù)切換”),這個 50 毫秒稱為“時間片”。而任務(wù)的切換大多數(shù)是在時間片段結(jié)束以后。

那么線程切換為什么會帶來bug呢?因為操作系統(tǒng)做任務(wù)切換,可以發(fā)生在任何一條CPU 指令執(zhí)行完!注意,是 CPU 指令,CPU 指令,CPU 指令,而不是高級語言里的一條語句。比如count++,在java里就是一句話,但高級語言里一條語句往往需要多條 CPU 指令完成。其實count++包含了三個CPU指令

 

有序性

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

在Java內(nèi)存模型中,為了效率是允許編譯器和處理器對指令進行重排序,當(dāng)然重排序不會影響單線程的運行結(jié)果,但是對多線程會有影響。Java提供volatile來保證一定的有序性。最著名的例子就是單例模式里面的DCL(雙重檢查鎖)。另外,可以通過synchronized和Lock來保證有序性,synchronized和Lock保證每個時刻是有一個線程執(zhí)行同步代碼,相當(dāng)于是讓線程順序執(zhí)行同步代碼,自然就保證了有序性。

在單線程的情況下,CPU執(zhí)行語句并不是按照順序來的,為了更高的執(zhí)行效率可能會重新排序,單線程下是可以提高執(zhí)行效率且保證正確。但在多線程下反而變成了安全問題,Java提供volatile來保證一定的有序性。此處不做深入!

 

volatile

volatile特性

  • 可見性:對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入
  • 原子性:對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復(fù)合操作不具有原子性

【面試題】為什么volatile不能保證a++的線程安全問題
:線程執(zhí)行a++要經(jīng)歷讀取主內(nèi)存-加載-使用-賦值-寫內(nèi)存-寫回主內(nèi)存幾個階段,而且a++不是原子操作,至少可以分為三步執(zhí)行。線程A、B同時從主內(nèi)存讀取a的值,A線程執(zhí)行到加載階段切換上下文交出CPU使用權(quán),B線程完成整個操作并刷新了主內(nèi)存中a的值。此時A線程繼續(xù)賦值等其他操作,已經(jīng)造成了安全問題。可見性是保證線程每次讀取時必須讀取主內(nèi)存的值,對后續(xù)的操作沒有限制,不會因為主內(nèi)存中的值改變而中斷了操作。如果是原子性則可以,synchronized可以保證原子性。

java 多線程與并發(fā)之volatile詳解分析

volatile 的實現(xiàn)原理

有volatile修飾的共享變量進行寫操作的時候會使用CPU提供的Lock前綴指令

  • 將當(dāng)前處理器緩存的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存
  • 這個寫回內(nèi)存的操作會使其他CPU里緩存了該地址的數(shù)據(jù)無效

單例模式的雙重鎖為什么要加volatile

public class TestInstance{
	private volatile static TestInstance instance;
	public static TestInstance getInstance(){        //1
		if(instance == null){                        //2
			synchronized(TestInstance.class){        //3
				if(instance == null){                //4
					instance = new TestInstance();   //5
				}
			}
		}
		return instance;                             //6
	}
}

需要volatile關(guān)鍵字的原因是,在并發(fā)情況下,如果沒有volatile關(guān)鍵字,在第5行會出現(xiàn)問題。
instance = new TestInstance()可以分解為3行偽代碼

a. memory = allocate() //分配內(nèi)存

b. ctorInstanc(memory) //初始化對象

c. instance = memory //設(shè)置instance指向剛分配的地址

上面的代碼在編譯運行時,可能會出現(xiàn)重排序從a-b-c排序為a-c-b。在多線程的情況下會出現(xiàn)以下問題。當(dāng)線程A在執(zhí)行第5行代碼時,B線程進來執(zhí)行到第2行代碼。假設(shè)此時A執(zhí)行的過程中發(fā)生了指令重排序,即先執(zhí)行了a和c,沒有執(zhí)行b。那么由于A線程執(zhí)行了c導(dǎo)致instance指向了一段地址,所以B線程判斷instance不為null,會直接跳到第6行并返回一個未初始化的對象

 

總結(jié)

因為CPU與內(nèi)存的速度差距越來越大,為了彌補速度差距引入了CPU緩存,又因為緩存導(dǎo)致線程安全問題,從前到后縷出一條線來就很容易理解了。如果只是單線程完全不擔(dān)心什么指令重排,想要更高的執(zhí)行效率必然付出安全風(fēng)險。知其然,知其所以然!

到此這篇關(guān)于java 多線程與并發(fā)之volatile詳解分析的文章就介紹到這了,更多相關(guān)Java volatile內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

原文鏈接:https://blog.csdn.net/xihuailu3244/article/details/115454622

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 久草热久草视频 | 中文国产在线视频 | 一区二区三区精品国产 | 日本一道aⅴ不卡免费播放 久久久久久久高清 | 久久久久亚洲视频 | 91九色免费视频 | 美女毛片儿 | 欧洲精品久久久久69精品 | 日本一道aⅴ不卡免费播放 日日草夜夜操 | 日韩电影网站 | 伊久在线 | 99久久精品免费 | 日本网站一区 | 在线播放免费视频 | 日本在线免费观看视频 | 国产污污视频 | 欧美成人一区二区视频 | 国产精品99久久久久久久女警 | 成人在线视频免费 | 国产91精品一区二区麻豆亚洲 | 国产91久久久 | 欧美日本中文字幕 | 久久伊人国产精品 | 韩国三级日本三级香港三级黄 | av影院在线 | 久久久久国产成人精品亚洲午夜 | 亚洲视频高清 | 国产精品久久久乱弄 | 精品久久久久久久久久久久久 | 永久免费av在线 | 久久久久久久久久久亚洲 | 91懂色| 久久国产一二区 | 国产精品视频免费看 | 久久综合爱 | 性欧美久久 | 精品亚洲成a人在线观看 | 性 毛片 | 96视频在线免费观看 | 毛毛片在线看 | 免费黄色短视频网站 |