卖逼视频免费看片|狼人就干网中文字慕|成人av影院导航|人妻少妇精品无码专区二区妖婧|亚洲丝袜视频玖玖|一区二区免费中文|日本高清无码一区|国产91无码小说|国产黄片子视频91sese日韩|免费高清无码成人网站入口

concurrenthashmap如何保證效率 java編程,如何徹底理解volatile關鍵字?

java編程,如何徹底理解volatile關鍵字?謝謝邀請~!下面解釋了用法、注意事項和基本原理!JMM基礎-計算機原理Java內(nèi)存模型被稱為Java內(nèi)存模型,簡稱JMM。JMM在計算機(RAM)中定

java編程,如何徹底理解volatile關鍵字?

謝謝邀請~!下面解釋了用法、注意事項和基本原理!

JMM基礎-計算機原理

Java內(nèi)存模型被稱為Java內(nèi)存模型,簡稱JMM。JMM在計算機(RAM)中定義了Java虛擬機(JVM)的工作模式。JVM是整個計算機的虛擬模型,所以JMM屬于JVM。

在計算機系統(tǒng)中,寄存器是L0緩存,其次是L1、L2和L3(其次是內(nèi)存、本地磁盤和遠程存儲)。緩存越高,存儲空間越小,速度越快,成本越高。越往下,存儲空間越大,速度越慢,成本越低。

從上到下,每一層都可以看作是下一層的緩存,即L0寄存器是L1一級緩存的緩存,

L1是L2的寶藏,等等;每一層的數(shù)據(jù)都來自下一層。

在目前的CPU上,一般來說,L0、L1、L2、L3都是繼承自CPU,L1也分為一級數(shù)據(jù)緩存和一級指令緩存,分別用于存儲數(shù)據(jù)和對數(shù)據(jù)進行指令解碼。每個核都有獨立的算術處理單元、控制器、寄存器、L1緩存和L2緩存,然后一個CPU的多個核共享最后一層CPU緩存L3。

CPU的緩存一致性解決方案

分為以下兩種方案

總線鎖(每次總線被鎖定,都是悲觀鎖)

緩存鎖(僅鎖定緩存的數(shù)據(jù))

《M:I(無效):,將stor:解鎖主存變量,解鎖后其他線程可以鎖定該變量。

Java內(nèi)存模型帶來的問題

1、能見度問題

運行在左CPU的線程將對象obj從主存復制到其CPU緩存中,并將對象obj的count變量改為2,但這個變化對于運行在右CPU的線程是不可見的,因為這個變化并沒有被刷新到主存中。

在多線程環(huán)境中,如果一個線程第一次讀取一個共享變量,它首先從主內(nèi)存中獲取它。變量,然后存儲在工作存儲器中,以后只需要讀取工作存儲器中的變量。同樣,如果變量被修改,新的值會先寫入工作內(nèi)存,然后刷新到內(nèi)存中,但最新的值什么時候刷新到主存中是不確定的,一般來說是很快的,但具體時間未知。要解決共享對象的可見性問題,我們可以使用volatile關鍵字或lock。

2.競爭問題

線程A和線程B共享一個對象obj。假設線程A將變量從主存讀入自己的緩存,線程B也將變量讀入自己的CPU緩存,兩個線程都加了1。此時,加1操作執(zhí)行了兩次,但兩次都在不同的CPU緩存中。

如果二加一的運算是串行執(zhí)行的,那么變量會在原來的值上加2,最后主存里的值就是3。那么圖中的二加一運算是并行的。無論是線程A還是線程B先把計算結果刷新到主存,主存都只會增加一次,變成2。雖然有二加一運算,但是我們可以用同步的代碼塊來解決上面的問題。

3.再訂購

除了共享內(nèi)存和工作內(nèi)存帶來的問題,還有重新排序的問題。當執(zhí)行程序時,編譯器和處理器經(jīng)常重新排序指令以提高性能。

重新排序分為三種類型:

(1)編譯器優(yōu)化的重新排序。

(2)指令級并行重排序

(3)記憶系統(tǒng)的重新排序

①數(shù)據(jù)依賴性

數(shù)據(jù)依賴:如果兩個操作訪問同一個變量,并且兩個操作中有一個是寫操作,那么這兩個操作之間就存在數(shù)據(jù)依賴。

依賴關系分為以下三種類型:

從上圖可以明顯看出,A和C有數(shù)據(jù)依賴,B和C也有數(shù)據(jù)依賴,但是A和B之間沒有數(shù)據(jù)依賴,如果重新安排A和C或者B和C的執(zhí)行順序,就會改變程序的執(zhí)行結果。

顯然,無論如何重新排序,都要保證代碼在單線程下正確運行,甚至是單線程下,更不用說討論多線程的并發(fā)性了,所以我們提出了as-if -serial的概念。

4、仿佛連載

無論如何重新排序(編譯器和處理器提高并行度),一個(單線程)程序的執(zhí)行結果都是無法改變的。編譯器、運行時和處理器都必須遵循模擬串行語義。

A和C之間有數(shù)據(jù)依賴,B和C之間也有數(shù)據(jù)依賴,所以在最后的執(zhí)行指令序列中,C可以 t在A和B之前重新排序(如果C在A和B之前,程序的結果會改變)。但是A和B之間沒有數(shù)據(jù)依賴,編譯器和處理器可以重新安排A和B之間的執(zhí)行順序..

仿佛系列語義菜單線程化的程序是有保護的,遵循as-if-serial語義的編譯器、運行時和處理器會讓我們覺得單線程程序好像是按照程序的順序執(zhí)行的。As-if-srial語義使得單線程程序不必擔心重新排序會干擾它們或內(nèi)存可見性。

5.記憶障礙

Java編譯器會在適當?shù)奈恢貌迦胍粋€內(nèi)存屏障來生成指令序列,禁止某些類型的處理重新排序,這樣程序就可以按照我們預期的進程執(zhí)行。

①保證具體操作的執(zhí)行順序。

②影響一些數(shù)據(jù)(或者一條指令的執(zhí)行結果)的內(nèi)存可見性。

編譯器和CPU可以對指令進行重新排序,確保最終得到相同的結果,并試圖優(yōu)化性能。插入一個內(nèi)存屏障會告訴編譯器和CPU,沒有指令可以用這個內(nèi)存屏障指令重新排序。

內(nèi)存屏障做的另一件事是強制刷新各種CPU緩存。例如,寫屏障會在其自身屏障之前清除寫入緩存的數(shù)據(jù),因此CPU上的任何線程都可以讀取這些數(shù)據(jù)的最新版本。

JMM將內(nèi)存屏障指令分為四類:

儲存量障礙是一個全方位的障礙,同時具有其他三個障礙的效果。

揮發(fā)性關鍵字介紹

1.確??梢娦?/p>

當讀取一個volatile變量時,您總是可以看到這個volatile變量的最后一次寫入(由任何線程執(zhí)行)。

讓 讓我們先看看下面的代碼:

InitFlag沒有用volatile關鍵字修飾;

以上結果是:

解釋一個線程改變initFlag的狀態(tài),而另一個線程可以 I don'我看不見。

如果加上volatile關鍵字呢?

結果如下:

讓 讓我們看看通過匯編實現(xiàn)代碼的最終底層實現(xiàn):

volatile編寫的內(nèi)存語義如下:

當寫入一個易變變量時,JMM會將線程對應的本地內(nèi)存中的共享變量值刷新到主內(nèi)存中。

當讀取一個易變變量時,JMM會使線程對應的本地內(nèi)存失效。線程接下來將從主內(nèi)存中讀取共享變量。

例如:

如果我們用volatile關鍵字修飾flag變量,那么實際上:線程A寫完flag變量后,線程A在本地內(nèi)存中更新的兩個共享變量的值都被刷新到主存中。

讀取標志變量后,本地存儲器B中包含的值已被設置為無效。此時,線程B必須從主內(nèi)存中讀取共享變量。線程B的讀操作會導致本地內(nèi)存B和主存享變量的值變得一致。

如果我們分兩步寫volatile和讀volatile,總之,在由讀取器線程B讀取一個可變變量之后,在由寫入器線程A寫入該可變變量之前,所有可見的共享變量的值將立即變得對讀取器線程B可見..

2.原子數(shù)

Volatile不保證變量的原子性;

運行結果如下:

因為計數(shù)

有三種操作:

(1)讀取變量計數(shù)

(2)將count變量的值加1。

(3)再次將計算值賦給變量count。

來自JMM的記憶分析:

讓 讓我們分析一下為什么易揮發(fā)的I can 不能保證字節(jié)碼的原子性。

javap :字節(jié)碼視圖

其實我這個操作主要可以分為三個步驟:(組裝)

將volatile變量的值讀取到local,增加變量的值,并將local的值寫回給其他線程查看。

從加載到存儲到內(nèi)存屏障有四個步驟。最后一步,jvm讓這個最新變量的值對所有線程可見,也就是最后一步讓所有CPU核都得到最新的值,但是中間的步驟(從加載到存儲)是不安全的,如果中間其他CPU修改了值,就會丟失。

3.整齊

(1)易變重排序規(guī)則表

①當?shù)诙€操作是易變寫時,無論第一個操作是什么,都不能重新排序。這個規(guī)則確保了在volatile寫入之前的操作不會在volatile寫入之后被編譯器重新排序。

②當?shù)谝粋€操作是volatile reading時,無論第二個操作是什么,都不能重新排序。這個規(guī)則確保在volatile讀取之后的操作不會在volatile讀取之前被編譯器重新排序。

③當?shù)谝粋€操作是易失性寫,第二個操作是易失性讀時,不能重新排序。

(2)易失性記憶障礙

(1)易變的寫作

Storestore barrier:對于這樣一個語句,store1 storestore store2,保證store1的寫操作在store2和后續(xù)寫操作執(zhí)行之前對其他處理器可見。(也就是說,如果出現(xiàn)storestore障礙,store1指令肯定會在store2之前執(zhí)行,CPU不會對store1和store2重新排序。)

Storeload barrier:對于這樣的語句store1 storeload load2,在執(zhí)行l(wèi)oad2和所有后續(xù)讀取操作之前,store1的寫入保證對所有處理器可見。(也就是說,如果出現(xiàn)storeload障礙,store1指令肯定會在load2之前執(zhí)行,CPU不會重新安排store1和load2。命令

②易變讀數(shù)

在每個易失性讀取操作后插入一個LoadLoad屏障。在每個易失性讀取操作后插入一個loadstore屏障。

Loadload barrier:對于這樣的語句,load1 Load load2,保證load1要讀取的數(shù)據(jù)在load2要讀取的數(shù)據(jù)和后續(xù)的讀取操作被訪問之前被讀取。(也就是說,如果出現(xiàn)loadload障礙,load1指令肯定會在load2之前執(zhí)行,CPU不會對load1和load2重新排序。)

Loadstore barrier:對于這樣一個語句,load1 loadstore store2,在store2和后續(xù)的寫操作被刷出之前,load1要讀取的數(shù)據(jù)保證被完全讀取。(也就是說,如果存在loadstore障礙,那么load1指令肯定會在store2之前執(zhí)行,CPU不會對load1和store2重新排序。)

揮發(fā)的實現(xiàn)原理

揮發(fā)的實現(xiàn)原理

?通過對OpenJDK中unsafe.cpp源代碼的分析,會發(fā)現(xiàn)會有前綴 "Locke CHO 6-@ . com "在由volatile關鍵字修改的變量中。

?鎖前綴,鎖不是記憶屏障,但可以完成類似記憶屏障的功能。Lock鎖定CPU總線和緩存,可以理解為CPU指令級的鎖。

?同時,指令會直接將當前處理器緩存行的數(shù)據(jù)寫入系統(tǒng)內(nèi)存,這個寫回操作會使緩存在這個地址的數(shù)據(jù)在其他CPU中失效。

?具體來說,它首先鎖定總線和緩存,然后執(zhí)行下面的指令,最后在釋放鎖定后將緩存中的所有臟數(shù)據(jù)刷新回主存。當鎖鎖定總線時,其他CPU的讀寫請求將被阻塞,直到鎖被釋放。

【歡迎密切關注@京京京京京,希望對你有幫助】

java concurrenthashmap put的時候要加鎖嗎?

不需要鎖。Java ConcurrentHashMap在內(nèi)部實現(xiàn)了鎖機制,ConcurrentHashMap類包含兩個靜態(tài)內(nèi)部類,HashEntry和Segment。HashEntry用于封裝映射表的鍵/值對;Segment用來充當鎖,每個Segment對象守護整個hash映射表的幾個桶。每個桶都是由幾個HashEntry對象鏈接的鏈表。ConcurrentHashMap實例包含幾個Segment對象的數(shù)組。