現在有乙個靜態變數 x:
static int x = 0;
執行緒a執行:
x = 2;
使用 volatile 修飾的變數對所有執行緒具有可見性,這就解決了我們上邊遇到的問題:當乙個執行緒改變了變數的值,會立刻同步到主記憶體中,其它執行緒讀取時,也會從主記憶體中得到最新的值。
volatile 的可見性是基於先行發生原則的,也就是說,對 volatile 變數的寫操作,先行發生於後面對這個變數的讀操作。
假設現在有執行緒a和執行緒b,同時對變數 x 做多次修改,雖然它們取到的值都是最新的,但修改操作並不是原子性的,可能從取值到修改完畢之間,變數值已經被修改了很多次,這時就相當於在舊的變數值上做修改,無法保證執行緒安全。
volatile int num = 10;
volatile int max = 20;
執行緒a:
while(num < max)
執行緒b:
num += 10;
max += 10;
如果執行緒a在執行時,執行緒b先修改了 num 的值為 20,還沒來得及修改 max,執行緒a的判斷就去取值,則會導致執行緒a終止。
在不影響程式執行結果的前提下(這裡指的是單執行緒中的執行結果),優化程式的執行效率。
在 jvm 編譯**,或 cpu 執行 jvm 位元組碼時,對指令進行重新排序。
// 初始化狀態
boolean finishinit = false;
執行緒a:
// 初始化
init();
// 將初始化狀態修改為 true
finishinit = true;
執行緒b:
while(!finishinit)
// 執行操作
exec();
這段**的意思是:執行緒a做初始化工作,完成後把標識修改為 true,執行緒b等到初始化狀態為 true 時,開始執行操作。
如果執行緒a被指令重排,很可能初始化狀態先變成了 true,才去執行 init() 方法,這就導致了執行緒b直接執行操作,由於沒有初始化完成而發生錯誤。
想要防止有序的**被指令重排,我們也可以使用 volatile 來修飾變數,它為我們插入了記憶體屏障。
記憶體屏障隔開了操作,防止屏障前後的操作被進行指令重排。
在屏障前的操作,將會在屏障後的操作之前執行。
屏障場景
作用loadload
讀1;屏障;讀2
讀2執行前,讀1要讀取完畢
storestore
寫1;屏障;寫2
寫2執行前,寫1的結果要對其它執行緒可見
loadstore
讀;屏障;寫
寫執行前,要先讀取完畢
storeload
寫;屏障;讀
讀執行前,寫的結果要對其它執行緒可見(開銷最大)
使用 volatile 修飾變數,jvm 會為變數的操作前後插入屏障:
這很容易理解:屏障的作用就是隔開操作,本次操作之前的,就是與本次操作一致的兩種操作形成的屏障;本次操作之後的,就是開頭與本操作一致,以另一種操作結尾形成的屏障。
以剛才程序初始化的執行緒a為例:
volatile boolean finishinit = false;
// 初始化
init();
// 將初始化狀態修改為 true
寫之前:storestore 屏障
finishinit = true;
寫之後:storeload 屏障
用 volatile 修飾變數,有 2 個作用: Java多執行緒 volatile詳解
1 可見性 2 禁止指令重排序 注 volatile的非原子性,在i 或i i 1 會出現不安全。1 lock字首指令會引起處理器快取寫到記憶體,執行緒的本地記憶體失效,別的執行緒只能從主存中讀取資料。而本地記憶體的值會立馬重新整理到主存中去。lock又分為鎖匯流排還是鎖快取 2 乙個處理器的快取回...
多執行緒 volatile
目錄 1.volatile關鍵字的兩層含義 2.volatile關鍵字的原理和實現機制 3.volatile關鍵字的使用場景 4.volatile關鍵字與synchronized關鍵字的比較 1 保證了不同執行緒對這個變數進行操作的可見性。2 禁止進行指令重排序,能在一定程度上保持有序性。volat...
多執行緒 volatile
volatile主要用途 1 保證可見性 對volatile變數的寫指令後會加入寫屏障 寫屏障 在屏障之前的對共享變數的改動都同步到主存 對volatile變數的讀指令前會加入讀屏障 讀屏障 在該屏障之後對共享變數的讀取載入的都是主存中的新資料 2 保證有序性 寫屏障保證指令重排序時,不會講寫屏障之...