volatile 關(guān)鍵字簡介
- 當多個線程進行操作共享數(shù)據(jù)時卿叽,可以保證內(nèi)存中的數(shù)據(jù)可見。
- 相較于 synchronized 是一種較為輕量級的同步策略固以。
Java語言提供了一種稍弱的同步機制掂僵,即volatile變量,用來確保將變量的更新操作通知到其他線程昆码。當把變量聲明為volatile類型后气忠,編譯器與運行時都會注意到這個變量是共享的,因此不會將該變量上的操作與其他內(nèi)存操作一起重排序赋咽。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方旧噪,因此在讀取volatile類型的變量時總會返回最新寫入的值。
在訪問volatile變量時不會執(zhí)行加鎖操作脓匿,因此也就不會使執(zhí)行線程阻塞淘钟,因此volatile變量是一種比sychronized關(guān)鍵字更輕量級的同步機制。
當對非 volatile 變量進行讀寫的時候陪毡,每個線程先從內(nèi)存拷貝變量到CPU緩存中米母。如果計算機有多個CPU勾扭,每個線程可能在不同的CPU上被處理,這意味著每個線程可以拷貝到不同的 CPU cache 中铁瞒。
而聲明變量是 volatile 的妙色,JVM 保證了每次讀變量都從內(nèi)存中讀,跳過 CPU cache 這一步慧耍。
當一個變量定義為 volatile 之后身辨,將具備兩種特性:
- 保證此變量對所有的線程的可見性,即當一個線程修改了這個變量的值芍碧,volatile 保證了新值能立即同步到主內(nèi)存煌珊,以及每次使用前立即從主內(nèi)存刷新。但普通變量做不到這點泌豆,普通變量的值在線程間傳遞均需要通過主內(nèi)存來完成定庵。
- 禁止指令重排序優(yōu)化。有volatile修飾的變量践美,賦值后多執(zhí)行了一個“l(fā)oad addl $0x0, (%esp)”操作洗贰,這個操作相當于一個內(nèi)存屏障(指令重排序時不能把后面的指令重排序到內(nèi)存屏障之前的位置),只有一個CPU訪問內(nèi)存時陨倡,并不需要內(nèi)存屏障敛滋;(指令重排序:是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應電路單元處理)。
volatile 性能:
- volatile 的讀性能消耗與普通變量幾乎相同兴革,但是寫操作稍慢绎晃,因為它需要在本地代碼中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行。
示例代碼:
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true){
if(td.isFlag()){
System.out.println("------------------");
break;
}
}
}
}
class ThreadDemo implements Runnable {
// 1. 不使用volatile關(guān)鍵字杂曲,子線程修改后變量后(更新子線程緩存庶艾,再更新主內(nèi)存),main線程沒有從主內(nèi)存中讀取擎勘,還是從main線程的緩存中讀取
private boolean flag = false;
// 2.使用volatile關(guān)鍵字咱揍,子線程修改后變量后(更新子線程緩存,再更新主內(nèi)存)棚饵,main線程每次跳過自己的緩存煤裙,而是從主內(nèi)存中讀取
// private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
flag = true;
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
volatile關(guān)鍵字缺陷
- volatile 不具備“互斥性”
- volatile 不能保證變量的“原子性”