激情久久久_欧美视频区_成人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的實現(xiàn)原理

深入分析java并發(fā)編程中volatile的實現(xiàn)原理

2021-02-06 12:00方騰飛 Java教程

這篇文章主要介紹了深入分析java并發(fā)編程中Volatile的實現(xiàn)原理,涉及Volatile的官方定義,實現(xiàn)原理,使用優(yōu)化等相關(guān)內(nèi)容,具有一定參考價值,需要的朋友可以了解下。

引言

在多線程并發(fā)編程中synchronized和Volatile都扮演著重要的角色,Volatile是輕量級的synchronized,它在多處理器開發(fā)中保證了共享變量的“可見性”。可見性的意思是當(dāng)一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。它在某些情況下比synchronized的開銷更小,本文將深入分析在硬件層面上Inter處理器是如何實現(xiàn)Volatile的,通過深入分析能幫助我們正確的使用Volatile變量。

術(shù)語定義

 

術(shù)語 英文單詞 描述
共享變量   在多個線程之間能夠被共享的變量被稱為共享變量。共享變量包括所有的實例變量,靜態(tài)變量和數(shù)組元素。他們都被存放在堆內(nèi)存中,Volatile只作用于共享變量。
內(nèi)存屏障 Memory Barriers 是一組處理器指令,用于實現(xiàn)對內(nèi)存操作的順序限制。
緩沖行 Cache line 緩存中可以分配的最小存儲單位。處理器填寫緩存線時會加載整個緩存線,需要使用多個主內(nèi)存讀周期。
原子操作 Atomic operations 不可中斷的一個或一系列操作。
緩存行填充 cache line fill 當(dāng)處理器識別到從內(nèi)存中讀取操作數(shù)是可緩存的,處理器讀取整個緩存行到適當(dāng)?shù)木彺妫↙1,L2,L3的或所有)
緩存命中 cache hit 如果進行高速緩存行填充操作的內(nèi)存位置仍然是下次處理器訪問的地址時,處理器從緩存中讀取操作數(shù),而不是從內(nèi)存。
寫命中 write hit 當(dāng)處理器將操作數(shù)寫回到一個內(nèi)存緩存的區(qū)域時,它首先會檢查這個緩存的內(nèi)存地址是否在緩存行中,如果存在一個有效的緩存行,則處理器將這個操作數(shù)寫回到緩存,而不是寫回到內(nèi)存,這個操作被稱為寫命中。
寫缺失 write misses the cache 一個有效的緩存行被寫入到不存在的內(nèi)存區(qū)域。

 

Volatile的官方定義

Java語言規(guī)范第三版中對volatile的定義如下: java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致的更新,線程應(yīng)該確保通過排他鎖單獨獲得這個變量。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個字段被聲明成volatile,java線程內(nèi)存模型確保所有線程看到這個變量的值是一致的。

為什么要使用Volatile

Volatile變量修飾符如果使用恰當(dāng)?shù)脑挘萻ynchronized的使用和執(zhí)行成本會更低,因為它不會引起線程上下文的切換和調(diào)度。

Volatile的實現(xiàn)原理

那么Volatile是如何來保證可見性的呢?在x86處理器下通過工具獲取JIT編譯器生成的匯編指令來看看對Volatile進行寫操作CPU會做什么事情。

 

Java代碼: instance = new Singleton();//instance是volatile變量
匯編代碼: 0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: lock addl $0×0,(%esp);

 

有volatile變量修飾的共享變量進行寫操作的時候會多第二行匯編代碼,通過查IA-32架構(gòu)軟件開發(fā)者手冊可知,lock前綴的指令在多核處理器下會引發(fā)了兩件事情。

將當(dāng)前處理器緩存行的數(shù)據(jù)會寫回到系統(tǒng)內(nèi)存。
這個寫回內(nèi)存的操作會引起在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效。
處理器為了提高處理速度,不直接和內(nèi)存進行通訊,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1,L2或其他)后再進行操作,但操作完之后不知道何時會寫到內(nèi)存,如果對聲明了Volatile變量進行寫操作,JVM就會向處理器發(fā)送一條Lock前綴的指令,將這個變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。但是就算寫回到內(nèi)存,如果其他處理器緩存的值還是舊的,再執(zhí)行計算操作就會有問題,所以在多處理器下,為了保證各個處理器的緩存是一致的,就會實現(xiàn)緩存一致性協(xié)議,每個處理器通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己緩存的值是不是過期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對應(yīng)的內(nèi)存地址被修改,就會將當(dāng)前處理器的緩存行設(shè)置成無效狀態(tài),當(dāng)處理器要對這個數(shù)據(jù)進行修改操作的時候,會強制重新從系統(tǒng)內(nèi)存里把數(shù)據(jù)讀到處理器緩存里。

這兩件事情在IA-32軟件開發(fā)者架構(gòu)手冊的第三冊的多處理器管理章節(jié)(第八章)中有詳細闡述。

Lock前綴指令會引起處理器緩存回寫到內(nèi)存。Lock前綴指令導(dǎo)致在執(zhí)行指令期間,聲言處理器的 LOCK# 信號。在多處理器環(huán)境中,LOCK# 信號確保在聲言該信號期間,處理器可以獨占使用任何共享內(nèi)存。(因為它會鎖住總線,導(dǎo)致其他CPU不能訪問總線,不能訪問總線就意味著不能訪問系統(tǒng)內(nèi)存),但是在最近的處理器里,LOCK#信號一般不鎖總線,而是鎖緩存,畢竟鎖總線開銷比較大。在8.1.4章節(jié)有詳細說明鎖定操作對處理器緩存的影響,對于Intel486和Pentium處理器,在鎖操作時,總是在總線上聲言LOCK#信號。但在P6和最近的處理器中,如果訪問的內(nèi)存區(qū)域已經(jīng)緩存在處理器內(nèi)部,則不會聲言LOCK#信號。相反地,它會鎖定這塊內(nèi)存區(qū)域的緩存并回寫到內(nèi)存,并使用緩存一致性機制來確保修改的原子性,此操作被稱為“緩存鎖定”,緩存一致性機制會阻止同時修改被兩個以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù)。

一個處理器的緩存回寫到內(nèi)存會導(dǎo)致其他處理器的緩存無效。IA-32處理器和Intel 64處理器使用MESI(修改,獨占,共享,無效)控制協(xié)議去維護內(nèi)部緩存和其他處理器緩存的一致性。在多核處理器系統(tǒng)中進行操作的時候,IA-32 和Intel 64處理器能嗅探其他處理器訪問系統(tǒng)內(nèi)存和它們的內(nèi)部緩存。它們使用嗅探技術(shù)保證它的內(nèi)部緩存,系統(tǒng)內(nèi)存和其他處理器的緩存的數(shù)據(jù)在總線上保持一致。例如在Pentium和P6 family處理器中,如果通過嗅探一個處理器來檢測其他處理器打算寫內(nèi)存地址,而這個地址當(dāng)前處理共享狀態(tài),那么正在嗅探的處理器將無效它的緩存行,在下次訪問相同內(nèi)存地址時,強制執(zhí)行緩存行填充。

Volatile的使用優(yōu)化

著名的Java并發(fā)編程大師Doug lea在JDK7的并發(fā)包里新增一個隊列集合類LinkedTransferQueue,他在使用Volatile變量時,用一種追加字節(jié)的方式來優(yōu)化隊列出隊和入隊的性能。

追加字節(jié)能優(yōu)化性能?這種方式看起來很神奇,但如果深入理解處理器架構(gòu)就能理解其中的奧秘。讓我們先來看看LinkedTransferQueue這個類,它使用一個內(nèi)部類類型來定義隊列的頭隊列(Head)和尾節(jié)點(tail),而這個內(nèi)部類PaddedAtomicReference相對于父類AtomicReference只做了一件事情,就將共享變量追加到64字節(jié)。我們可以來計算下,一個對象的引用占4個字節(jié),它追加了15個變量共占60個字節(jié),再加上父類的Value變量,一共64個字節(jié)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** head of the queue */
private transient final PaddedAtomicReference<QNode> head;
/** tail of the queue */
private transient final PaddedAtomicReference<QNode> tail;
static final class PaddedAtomicReference <T> extends AtomicReference <T> {
    // enough padding for 64bytes with 4byte refs
    Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;
    PaddedAtomicReference(T r) {
        super(r);
    }
}
public class AtomicReference <V> implements java.io.Serializable {
    private volatile V value;
    //省略其他代碼
    

為什么追加64字節(jié)能夠提高并發(fā)編程的效率呢? 因為對于英特爾酷睿i7,酷睿, Atom和NetBurst, Core Solo和Pentium M處理器的L1,L2或L3緩存的高速緩存行是64個字節(jié)寬,不支持部分填充緩存行,這意味著如果隊列的頭節(jié)點和尾節(jié)點都不足64字節(jié)的話,處理器會將它們都讀到同一個高速緩存行中,在多處理器下每個處理器都會緩存同樣的頭尾節(jié)點,當(dāng)一個處理器試圖修改頭接點時會將整個緩存行鎖定,那么在緩存一致性機制的作用下,會導(dǎo)致其他處理器不能訪問自己高速緩存中的尾節(jié)點,而隊列的入隊和出隊操作是需要不停修改頭接點和尾節(jié)點,所以在多處理器的情況下將會嚴重影響到隊列的入隊和出隊效率。Doug lea使用追加到64字節(jié)的方式來填滿高速緩沖區(qū)的緩存行,避免頭接點和尾節(jié)點加載到同一個緩存行,使得頭尾節(jié)點在修改時不會互相鎖定。

那么是不是在使用Volatile變量時都應(yīng)該追加到64字節(jié)呢?不是的。在兩種場景下不應(yīng)該使用這種方式。第一:緩存行非64字節(jié)寬的處理器,如P6系列和奔騰處理器,它們的L1和L2高速緩存行是32個字節(jié)寬。第二:共享變量不會被頻繁的寫。因為使用追加字節(jié)的方式需要處理器讀取更多的字節(jié)到高速緩沖區(qū),這本身就會帶來一定的性能消耗,共享變量如果不被頻繁寫的話,鎖的幾率也非常小,就沒必要通過追加字節(jié)的方式來避免相互鎖定。

總結(jié)

以上就是本文關(guān)于深入分析java并發(fā)編程中Volatile的實現(xiàn)原理的全部內(nèi)容,希望對大家有所幫助。如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!

原文鏈接:http://www.importnew.com/17394.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 精品久久久久久久久久久下田 | 日本精品一二区 | 国产成人高潮免费观看精品 | 天天草天天干天天射 | 午夜视频久久 | 久久超 | 欧美黄色试片 | 成年人免费视频大全 | 免费一级特黄做受大片 | 国产成人视屏 | 黄色片网站在线看 | 综合网天天射 | 久久国产一二三 | 嫩呦国产一区二区三区av | 最新亚洲国产 | 国产九色在线观看 | 国产成人高清在线观看 | 欧美性生活网站 | videos真实高潮xxxx | 欧美成人精品一区二区 | 国产一区二区三区高清 | 国产精品一区二区日韩 | 久草视频福利在线观看 | 久产久精品| 韩国一级免费视频 | 精品国产91久久久久久久 | 欧美一区欧美二区 | 麻豆国产网站 | 成熟女人特级毛片www免费 | 中文日韩在线视频 | 午夜精品成人一区二区 | 免费香蕉成视频成人网 | 蜜桃久久一区二区三区 | 成年免费视频黄网站在线观看 | 日韩精品中文字幕一区二区三区 | 亚洲国产精品久久久久久久久 | 亚洲啊v在线观看 | 亚洲5区 | 91 在线视频观看 | 被狠狠操| 姑娘第四集免费看视频 |