Java(多執行緒) volatile

2021-09-02 00:20:36 字數 1893 閱讀 1825

現在有乙個靜態變數 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 保證有序性 寫屏障保證指令重排序時,不會講寫屏障之...