漫畫:什么是 volatile 關鍵字?
801
2025-04-01
volatile 的實現維度
可見性問題
讓一個線程對共享變量的修改,能夠及時的被其他線程看到。
根據JMM中規定的happen before和同步原則:
對某個volatile字段的寫操作happens- before每個后續對該volatile字段的讀操作。
對volatile變量v的寫入,與所有其他線程后續對v的讀同步
要滿足這些條件,所以volatile關鍵字就有這些功能:
禁止緩存;
volatile變量的訪問控制符會加個ACC_VOLATILE
對volatile變 量相關的指令不做重排序
volatile 變量可以被看作是一種 "輕量的 synchronized,可算是JVM提供的最輕量級的同步機制。
當一個變量定義為volatile后,可以保證此變量對所有線程的可見性。
2 原子性(Atomicity)
一次只允許一個線程持有某鎖,一次只有一個線程能使用共享數據
由JMM直接保證的原子性變量操作包括read、load、use、assign、store和write六個,大致可以認為基礎數據類型的訪問讀寫是原子性的
如果應用場景需要一個更大范圍的原子性保證,JMM還提供了lock和unlock操作來滿足這種需求,盡管虛擬機未把lock與unlock操作直接開放給用戶使用,但是卻提供了更高層次的字節碼指令monitorenter和monitorexit來隱匿地使用這兩個操作,這兩個字節碼指令反映到Java代碼中就是同步塊synchronized關鍵字,因此在synchronized塊之間的操作也具備原子性
5 可見性(Visibility)
當一個線程修改了線程共享變量的值,其它線程能夠立即得知這個修改。
由于現代可共享內存的多處理器架構可能導致一個線程無法馬上看到另一個線程操作產生的結果。所以 Java 內存模型規定了 JVM 的一種最小保證:什么時候寫入一個變量對其他線程可見。
在現代可共享內存的多處理器體系結構中每個處理器都有自己的緩存,并周期性的與主內存協調一致。假設線程 A 寫入一個變量值 V,隨后另一個線程 B 讀取變量 V 的值
在下列情況下,線程 B 讀取的值可能不是線程 A 寫入的最新值:
執行線程 A 的處理器把變量 V 緩存到寄存器中。
執行線程 A 的處理器把變量 V 緩存到自己的緩存中,但還沒有同步刷新到主內存中去。
執行線程 B 的處理器的緩存中有變量 V 的舊值。
JMM通過在變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值這種依賴主內存作為傳遞媒介的方法來實現可見性,無論是普通變量還是volatile變量都是如此。
普通變量與volatile變量的區別:
volatile保證了新值能立即同步到主內存,以及每使用前立即從內存刷新。因此volatile保證了線程操作時變量的可見性,而普通變量則不能保證。
除了volatile,Java還有兩個關鍵字能實現可見性:
synchronized
由“對一個變量執行unlock前,必須先把此變量同步回主內存中(執行store和write)”這條規則獲得的
final
被final修飾的字段在構造器中一旦初始化完成,并且構造器沒有把"this"的引用傳遞出去(this引用逃逸是一件很危險的事情,其他線程有可能通過這個引用訪問到“初始化了一半”的對象),那在其他線程中就能看見final字段的值
final在該對象的構造函數中設置對象的字段,當線程看到該對象時,將始終看到該對象的final字段的正確構造版本。
偽代碼示例
f = new finalDemo();
1
讀取到的 f.x 一定最新,x為final字段。
如果在構造函數中設置字段后發生讀取,則會看到該final字段分配的值,否則它將看到默認值;
偽代碼示例:
public finalDemo(){x=1;y=x;};
1
y會等于1;
讀取該共享對象的final成員變量之前,先要讀取共享對象。
偽代碼示例:
r= new ReferenceObj(); k=r.f;
1
2
這兩個操作不能重排序
通常static final是不可以修改的字段。然而System.in, System.out和System.err 是static final字段,遺留原因,必須允許通過set方法改變,我們將這些字段稱為寫保護,以區別于普通final字段
必須確保釋放鎖之前對共享數據做出的更改對于隨后獲得該鎖的另一個線程可見,對域中的值做賦值和返回的操作通常是原子性的,但遞增/減并不是
volatile對所有線程是立即可見的,對volatile變量所有的寫操作都能立即返回到其它線程之中,換句話說,volatile變量在各個線程中是一致的,但并非基于volatile變量的運算在并發下是安全的
volatile變量在各線程的工作內存中不存在一致性問題(在各個線程的工作內存中volatile變量也可以存在不一致,但由于
每次使用之前都要先刷新 ,執行引擎看不到不一致的情況,因此可以認為不存在一致性問題),但Java里的運算并非原子操作,導致volatile變量的運算在并發下一樣是不安全的
public class Atomicity { int i; void f(){ i++; } void g(){ i += 3; } }
1
2
3
4
5
6
7
8
9
編譯后文件
void f(); 0 aload_0 [this] 1 dup 2 getfield concurrency.Atomicity.i : int [17] 5 iconst_1 6 iadd 7 putfield concurrency.Atomicity.i : int [17] // Method descriptor #8 ()V // Stack: 3, Locals: 1 void g(); 0 aload_0 [this] 1 dup 2 getfield concurrency.Atomicity.i : int [17] 5 iconst_3 6 iadd 7 putfield concurrency.Atomicity.i : int [17] }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
每個操作都產生了一個 get 和 put ,之間還有一些其他的指令
因此在獲取和修改之間,另一個線程可能會修改這個域
所以,這些操作不是原子性的
再看下面這個例子是否符合上面的描述
public class AtomicityTest implements Runnable { private int i = 0; public int getValue() { return i; } private synchronized void evenIncrement() { i++; i++; } public void run() { while(true) evenIncrement(); } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); AtomicityTest at = new AtomicityTest(); exec.execute(at); while(true) { int val = at.getValue(); if(val % 2 != 0) { System.out.println(val); System.exit(0); } } } } output: 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
該程序將找到奇數值并終止
盡管return i原子性,但缺少同步使得其數值可以在處于不穩定的中間狀態時被讀取
由于 i 不是 volatile ,存在可視性問題
getValue() 和 evenIncrement() 必須synchronized
對于基本類型的讀/寫操作被認為是安全的原子性操作
但當對象處于不穩定狀態時,仍舊很有可能使用原子性操作來訪問他們
最明智的做法是遵循同步的規則
volatile 變量只保證可見性
在不符合以下條件規則的運算場景中,仍需要通過加鎖(使用synchronized或JUC中的原子類)來保證原子性
運算結果不依賴變量的當前值,或者能確保只有單一的線程修改變量的值
變量不需要與其它的狀態變量共同參與不可變類約束
基本上,若一個域可能會被多個任務同時訪問or這些任務中至少有一個是寫任務,那就該將此域設為volatile
當一個域定義為 volatile 后,將具備
1.保證此變量對所有的線程的可見性,當一個線程修改了這個變量的值,volatile 保證了新值能立即同步到主內存,其它線程每次使用前立即從主內存刷新
但普通變量做不到這點,普通變量的值在線程間傳遞均需要通過主內存來完成
2.禁止指令重排序。有volatile修飾的變量,賦值后多執行了一個“load addl
2.禁止指令重排序。有volatile修飾的變量,賦值后多執行了一個“load addl $0x0, (%esp)”操作,這個操作相當于一個內存屏障(指令重排序時不能把后面的指令重排序到內存屏障之前的位置)
x0, (%esp)”操作,這個操作相當于一個內存屏障(指令重排序時不能把后面的指令重排序到內存屏障之前的位置)這些操作的目的是用線程中的局部變量維護對該域的精確同步
6 CPU 性能優化手段 - 運行時指令重排序
編譯器生成指令的次序,可以不同于源代碼所暗示的“顯然”版本。
重排后的指令,對于優化執行以及成熟的全局寄存器分配算法的使用,都是大有脾益的,它使得程序在計算性能上有了很大的提升。
6.1 指令重排的場景
當CPU寫緩存時發現緩存區塊正被其他CPU占用,為了提高CPU處理性能, 可能將后面的讀緩存命令優先執行
比如:
并非隨便重排,需要遵守
as-if-serial語義
不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執行結果不能被改變。
編譯器,runtime 和處理器都必須遵守as-if- serial語義。
也就是說:編譯器和處理器不會對存在數據依賴關系的操作做重排
6.2 重排序類型
包括如下:
編譯器生成指令的次序,可以不同于源代碼所暗示的“顯然”版本。
處理器可以亂序或者并行的執行指令。
緩存會改變寫入提交到主內存的變量的次序。
問題
CPU執行指令重排序優化下有一個問題:
雖然遵守了as-if-serial語義,單僅在單CPU自己執行的情況下能保證結果正確。
多核多線程中,指令邏輯無法分辨因果關聯,可能出現亂序執行,導致程序運行結果錯誤。
有序性:即程序執行的順序按照代碼的先后順序執行
使用volatile變量的第二個語義是禁止指令重排序優化
普通變量僅保證該方法執行過程所有依賴賦值結果的地方能獲取到正確結果,而不保證變量賦值操作的順序與代碼執行順序一致
因為在一個線程的方法執行過程中無法感知到這一點,這也就是JMM中描述的所謂的
線程內表現為串行的語義(Within-Thread As-If-Serial Sematics)
Map configOptions; char[] configText; //此變量必須定義為volatile volatile boolean initialized = false; //假設以下代碼在線程A中執行 //模擬讀取配置信息,當讀取完成后 //將initialized設置為true來通知其它線程配置可用 configOptions = new HashMap(); configText = readConfigFile(fileName); processConfigOptions(configText, configOptions); initialized = true; //假設以下代碼在線程B中執行 //等線程A待initialized為true,代表線程A已經把配置信息初始化完成 while(!initialized) { sleep(); } //使用線程A中初始化好的配置信息 doSomethingWithConfig();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如果定義initialized時沒有使用volatile,就可能會由于指令重排序優化,導致位于線程A中最后一行的代碼initialized = true被提前執行,這樣在線程B中使用配置信息的代碼就可能出現錯誤,而volatile關鍵字則可以完美避免
volatile變量讀操作性能消耗與普通變量幾乎無差,但寫操作則可能會稍慢,因為它需要在代碼中插入許多內存屏障指令來保證處理器不發生亂序執行
不過即便如此,大多數場景下volatile的總開銷仍然要比鎖小,我們在volatile與鎖之中選擇的唯一依據僅僅是volatile的語義能否滿足使用場景的需求
volatile修飾的變量,賦值后(前面mov %eax,0x150 (%esi) 這句便是賦值操作) 多執行了一個1ock add1 $ 0x0,(%esp),這相當于一個內存屏障(Memory Barrier/Fence,指重排序時不能把后面的指令重排序到內存屏障之前的位置),只有一個CPU 訪問內存時,并不需要內存屏障
但如果有兩個或更多CPU 訪問同一塊內存,且其中有一個在觀測另一個,就需要內存屏障來保證一致性了
這句指令中的add1 $0x0, (%esp)(把ESP 寄存器的值加0) 顯然是一個空操作(采用這個空操作而不是空操作指令nop 是因為IA32手冊規定lock前綴不允許配合nop 指令使用),關鍵在于lock 前綴,查詢IA32 手冊,它的作用是使得本CPU 的Cache寫入內存,該寫入動作也會引起別的CPU 或者別的內核無效化(Inivalidate) 其Cache,這種操作相當于對Cache 中的變量做了一次store和write。所以通過這樣一個空操作,可讓前面volatile 變量的修改對其他CPU 立即可見。
那為何說它禁止指令重排序呢?
硬件架構上,指令重排序指CPU 采用了允許將多條指令不按程序規定的順序分開發送給各相應電路單元處理。但并不是說指令任意重排,CPU需要能正確處理指令依賴情況以保障程序能得出正確的執行結果
譬如指令1把地址A中的值加10,指令2把地址A 中的值乘以2,指令3把地址B 中的值減去了,這時指令1和指令2是有依賴的,它們之間的順序不能重排,(A+10) 2 與A2+10顯然不等,但指令3 可以重排到指令i、2之前或者中間,只要保證CPU 執行后面依賴到A、B值的操作時能獲取到正確的A 和B 值即可。所以在本CPU 中,重排序看起來依然是有序的。因此lock add1 $0x0,(%esp) 指令把修改同步到內存時,意味著所有之前的操作都已經執行完成,這樣便形成了“指令重排序無法越過內存屏障”的效果
舉個例子
int i = 0; boolean flag = false; i = 1; //語句1 flag = true; //語句2
1
2
3
4
從代碼順序上看,語句1在2前,JVM在真正執行這段代碼的時候會保證**語句1一定會在語句2前面執行嗎?不一定,為什么呢?這里可能會發生指令重排序(Instruction Reorder)
比如上面的代碼中,語句1/2誰先執行對最終的程序結果并無影響,就有可能在執行過程中,語句2先執行而1后雖然處理器會對指令進行重排序,但是它會保證程序最終結果會和代碼順序執行結果相同,**靠什么保證?數據依賴性
編譯器和處理器在重排序時,會遵守數據依賴性,編譯器和處理器不會改變存在數據依賴關系的兩個操作的執行順序
舉例
double pi = 3.14; //A double r = 1.0; //B double area = pi * r * r; //C
1
2
3
A和C之間存在數據依賴關系,同時B和C之間也存在數據依賴關系。
因此在最終執行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的結果將會被改變)。
但A和B之間沒有數據依賴關系,編譯器和處理器可以重排序A和B之間的執行順序
這里所說的數據依賴性僅針對單個處理器中執行的指令序列和單個線程中執行的操作,在單線程程序中,對存在控制依賴的操作重排序,不會改變執行結果
但在多線程程序中,對存在控制依賴的操作重排序,可能會改變程序的執行結果。這是就需要內存屏障來保證可見性了
回頭看一下JMM對volatile 變量定義的特殊規則
假定T 表示一個線程,V 和W 分別表示兩個volatile變量,那么在進行read, load, use,assign,store,write時需要滿定如下規則
只有當線程T 對變量V 執行的前一個動作是load ,線程T 方能對變量V 執行use;并且,只有當線程T 對變量V 執行的后一個動作是use,線程T才能對變量V執行load.線程T 對變量V 的use可認為是和線程T對變量V的load,read相關聯,必須連續一起出現(這條規則要求在工作內存中,每次使用V前都必須先從主內存刷新最新的值語,用于保證能看見其他線程對變量V所做的修改后的值)
只有當線程T 對變量V 執行的前一個動作是 assign ,線程T才能對變量V 執行store
并且,只有當線程T對變量V執行的后一個動作是store ,線程T才能對變量V執行assign
線程T對變量V的assign可以認為是和線程T對變量V的store,write相關聯,必須連續一起出現(這條規則要求在工作內存中,每次修改V 后都必須立刻同步回主內存中,用于保證其他線程可以看到自己對變量V所做的修改)
假定動作A 是線程T 對變量V實施的use或assign,假定動作F 是和動作A 相關聯的load或store,假定動作P 是和動作F 相應的對變量V 的read 或write
類似的,假定動作B 是線程T 對變量W 實施的use或assign 動作,假定動作G是和動作B 相關聯的load或store,假定動作Q 是和動作G 相應的對變量W的read或write
如果A 先于B,那么P先于Q (這條規則要求volatile修飾的變量不會被指令重排序優化,保證代碼的執行順序與程序的順序相同)
對于Long和double型變量的特殊規則
虛擬機規范中,寫64位的double和long分成了兩次32位值的操作
由于不是原子操作,可能導致讀取到某次寫操作中64位的前32位,以及另外一次寫操作的后32位
讀寫volatile的long和double總是原子的。讀寫引用也總是原子的
商業JVM不會存在這個問題,雖然規范沒要求實現原子性,但是考慮到實際應用,大部分都實現了原子性。
對于32位平臺,64位的操作需要分兩步來進行,與主存的同步。所以可能出現“半個變量”的狀態。
在實際開發中,目前各種平臺下的商用虛擬機幾乎都選擇把64位數據的讀寫操作作為原子操作來對待,因此我們在編碼時一般不需要把用到的long和double變量專門聲明為volatile。
Word Tearing字節處理
一個字段或元素的更新不得與任何其他字段或元素的讀取或更新交互。
特別是,分別更新字節數組的相鄰元素的兩個線程不得干涉或交互,也不需要同步以確保順序一致性。
有些處理器(尤其是早期的Alphas處理器)沒有提供寫單個字節的功能。
在這樣的處理器_上更新byte數組,若只是簡單地讀取整個內容,更新對應的字節,然后將整個內容再寫回內存,將是不合法的。
這個問題有時候被稱為“字分裂(word tearing)”,在單獨更新單個字節有難度的處理器上,就需要尋求其它方式了。
基本不需要考慮這個,了解就好。
JAVA代碼層級 - volatile JVM層級 - JSR os - 具體實現
1
2
3
內存屏障
JSR的內存屏障(JVM 規范)
這只是 JVM 層級的要求,非底層硬件的具體實現!
在 volatile 讀寫前后都加上屏障
LoadLoad屏障
對于這樣的語句Load1; Loadload; Load2,
在Load2及后續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢
StoreStore屏障
對于這樣的語句Store1; StoreStore; Store2,
在Store2及后續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
LoadStore屏障
對于這樣的語甸oad1; LoadStore; Store2,
在Store2及后續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
Storeload屏障
對于這樣的語句Store1; StoreL oad; Ioad2,
在Load2及后續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。
JVM 層面 volatile的實現細節
x86 CPU 內存屏障 - 原語級別實現
之所以JVM不直接使用這些指令,是因為并非所有 cpu 都支持,但是所有 cpu 都支持 lock 指令!
lock 指令直接鎖定總線,肯定直接禁止了重排序,因此 JVM是調用了該指令,簡單暴力!
sfence
在sfence指令前的寫操作當必須在sfence指令后的寫操作前完成
lfence
在Ifence指令前的讀操作當必須在Ifence指令后的讀操作前完成
mfence
在mfence指令前的讀寫操作當必須在mfence指令后的讀寫操作前完成。
有序性保障:intel lock 匯編指令
原子指令,如x86上的lock指令是一個Full Barrier,執行時會鎖住內存子系統來確保執行順序,甚至跨多個CPU。Software Locks通常使用了內存屏障或原子指令來實現變量可見性和保持程序順序
處理器提供了兩個內存屏障指令(Memory Barrier)用于解決上述的兩個問題:
7.1 指令分類
寫內存屏障(Store Memory Barrier)
在指令后插入Store Barrier,能讓寫入緩存中的最新數據更新寫入主內存,讓其他線程可見
強制寫入主內存,這種顯示調用,CPU就不會因為性能考慮而去對指令重排
讀內存屏障(Load Memory Barrier)
在指令前插入Load Barrier,可以讓高速緩存中的數
據失效,強制從新從主內存加載數據。
強制讀取主內存內容,讓CPU緩存與主內存保持一致,避免了緩存導致的一致性問題
7.2 有序性(Ordering)
JMM中程序的天然有序性可以總結為一句話:
如果在本線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有操作都是無序的。
前半句是指“線程內表現為串行語義”
后半句是指“指令重排序”現象和“工作內存主主內存同步延遲”現象
Java提供了volatile和synchronized保證線程之間操作的有序性
volatile本身就包含了禁止指令重排序的語義
synchronized則是由“一個變量在同一時刻只允許一條線程對其進行lock操作”這條規則來獲得的,這個規則決定了持有同一個鎖的兩個同步塊只能串行地進入。
7.3 Happens-beofre 先行發生原則(JVM 規范)
這八種不能指令重排序
如果JMM中所有的有序性都只靠volatile和synchronized,那么有一些操作將會變得很繁瑣,但我們在編寫Java并發代碼時并沒有感到這一點,這是因為Java語言中有一個先行發生(Happen-Before)原則。它是判斷數據是否存在競爭,線程是否安全的主要依賴。
先行發生原則
JMM中定義的兩項操作之間的依序關系。
happens- before關系 主要是強調兩個有沖突的動作之間的順序,以及定義數據爭用的發生時機。
如果操作A先行發生于操作B,就是在說發生B前,A產生的影響能被B觀察到,“影響”包含了修改內存中共享變量的值、發送了消息、調用了方法等。案例如下:
// 線程A中執行 i = 1; // 線程B中執行 j = i; // 線程C中執行 i = 2;
1
2
3
4
5
6
7
8
下面是JMM下一些”天然的“先行發生關系,無須任何同步器協助就已經存在,可以在編碼中直接使用。
如果兩個操作之間的關系不在此列,并且無法從下列規則推導出來的話,它們就沒有順序性保障,虛擬機可以對它們進行隨意重排序。
具體的虛擬機實現,有必要確保以下原則的成立:
程序次序規則(Pragram Order Rule)
在一個線程內,按照代碼順序,書寫在前面的操作先行發生于書寫在后面的操作。準確地說應該是控制流順序而不是程序代碼順序,因為要考慮分支、循環結構。
對象鎖(監視器鎖)法則(Monitor Lock Rule )
某個管程(也叫做對象鎖,監視器鎖) 上的unlock動作happens-before同一個管程上后續的lock動作 。這里必須強調的是同一個鎖,而”后面“是指時間上的先后。
volatile變量規則(Volatile Variable Rule)
對某個volatile字段的寫操作happens- before每個后續對該volatile字段的讀操作,這里的”后面“同樣指時間上的先后順序。
線程啟動規則(Thread Start Rule)
在某個線程對象 上調用start()方法happens- before該啟動了的線程中的任意動作
線程終止規則(Thread Termination Rule)
某線程中的所有操作都先行發生于對此線程的終止檢測,我們可以通過Thread.join()方法結束(任意其它線程成功從該線程對象上的join()中返回),Thread.isAlive()的返回值等作段檢測到線程已經終止執行。
線程中斷規則(Thread Interruption Rule)
對線程interrupt()方法的調用先行發生于被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測是否有中斷發生
對象終結規則(Finalizer Rule)
一個對象初始化完成(構造方法執行完成)先行發生于它的finalize()方法的開始
傳遞性(Transitivity)
如果操作A先行發生于操作B,操作B先行發生于操作C,那就可以得出操作A先行發生于操作C的結論
一個操作”時間上的先發生“不代表這個操作會是”先行發生“,那如果一個操作”先行發生“是否就能推導出這個操作必定是”時間上的先發生“呢?也是不成立的,一個典型的例子就是指令重排序。
所以時間上的先后順序與先行發生原則之間基本沒有什么關系,所以衡量并發安全問題一切必須以先行發生原則為準。
7.4 作用
1.阻止屏障兩側的指令重排序
2.強制把寫緩沖區/高速緩存中的臟數據等寫回主內存,讓緩存中相應的數據失效
對于Load Barrier來說,在指令前插入Load Barrier,可以讓高速緩存中的數據失效,強制從新從主內存加載數據
對于Store Barrier來說,在指令后插入Store Barrier,能讓寫入緩存中的最新數據更新寫入主內存,讓其他線程可見
Java的內存屏障實際上也是上述兩種的組合,完成一系列的屏障和數據同步功能
LoadLoad屏障: 對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
StoreStore屏障: 對于這樣的語句Store1; StoreStore; Store2,在Store2及后續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
LoadStore屏障: 對于這樣的語句Load1; LoadStore; Store2,在Store2及后續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
StoreLoad屏障: 對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功能
volatile的內存屏障策略非常嚴格保守
在每個volatile寫操作前插入StoreStore屏障,在寫操作后插入StoreLoad屏障
在每個volatile讀操作前插入LoadLoad屏障,在讀操作后插入LoadStore屏障
由于內存屏障的作用,避免了volatile變量和其它指令重排序、線程之間實現了通信,使得volatile表現出了鎖的特性。
總結
看到了現代CPU不斷演進,在程序運行優化中做出的努力。不同CPU廠商所付出的人力物力成本,最終體現在不同CPU性能差距上。而Java就隨即推出了大量保證線程安全的機
Java JVM 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。