volatile 關鍵字(詳解)
volatile 關鍵字
說明volatile 關鍵字之前,首先先簡單介紹下java記憶體模型,因為後續的介紹與java的記憶體模型息息相關。
詳細的就不說明了,百度上都有,簡單的說下。
Java 記憶體模型中的可見性、原子性和有序性。
可見性: 可見性,是指執行緒之間的可見性,一個執行緒修改的狀態對另一個執行緒是可見的。
原子性: 原子是世界上的最小單位,具有不可分割性。
有序性: 即程式執行的順序按照程式碼的先後順序執行。
volatile,從字面上說是易變的、不穩定的,事實上,也確實如此,這個關鍵字的作用就是告訴編譯器,
只要是被此關鍵字修飾的變數都是易變的、不穩定的
volatile關鍵字的兩個特點:
(1)、記憶體可見性,即執行緒A對volatile變數的修改,其他執行緒獲取的volatile變數都是最新的。
(2)、可以禁止指令重排序
首先說下第一個volatile特點,假如不加這個關鍵字會怎麼樣,如:
public class test{
private int key;
public int getKey(){
return key;
}
public void setKey(int key){
this.key= key;
}
}
在這個程式碼中,test不是執行緒安全的,因為key的set/get方法都是在沒有同步的執行緒中執行的,假如說有兩個執行緒同步執行,
執行緒1執行了set方法,執行緒2的get方法有可能取到值也有可能取不到值,解決辦法,就是在成員變數那塊加上volatile關鍵字,這樣的話,利用volatile關鍵字第一個特性,當執行緒1執行了set方法的時候,執行緒2的get方法也可以取到值。
以下有個列子,
說明了volatile保證不了原子性這個特點:
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class test4 { /** * 1)保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。 * 2)禁止進行指令重排序。 * **/ public volatile int inc = 0; //沒有達到預期原因是volatile關鍵字保證了java記憶體模型中的可讀性,但遞增操作保證不了原子性 public void increaseVolatile() { inc++; } public static void main(String[] args) { final test4 test = new test4(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++){ test.increaseVolatile(); } }; }.start(); } while(Thread.activeCount()>1) //保證前面的執行緒都執行完 Thread.yield(); System.out.println("volatile------"+test.inc); /** * 結論: * volatile關鍵字可以保證目標的可讀性,但是保證不了目標的原子性 * * **/ } }
通過上面的這段程式碼可以猜出,正常來說它的值應該等於10000,但是執行出的結果卻是可能等於10000,也有可能小於10000
如何解決,可以採用synchronized關鍵字達到預期目的,或者採用Lock達到預期目的,再或者採用AtomicInteger達到預期目的,下面是個對比的列子可以看成視覺化的結果:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test4 {
/**
* 1)保證了不同執行緒對這個變數進行操作時的可見性,即一個執行緒修改了某個變數的值,這新值對其他執行緒來說是立即可見的。
* 2)禁止進行指令重排序。
* **/
public volatile int inc = 0; //沒有達到預期原因是volatile關鍵字保證了java記憶體模型中的可讀性,但遞增操作保證不了原子性
public int incc = 0; //採用synchronized關鍵字達到預期目的
public int inccc = 0; //採用Lock達到預期目的
Lock lock = new ReentrantLock();
public AtomicInteger incccc = new AtomicInteger(); //採用AtomicInteger達到預期目的
public synchronized static void increaseSynchronized() { //這裡包含相關類鎖和物件鎖的區別的知識,當有多個執行緒共同應 //用的時候,使用類鎖。
incc++;
}
public void increaseVolatile() {
inc++;
}
public void increaseLock() {
lock.lock();
try {
inccc++;
} finally{
lock.unlock();
}
}
public void increaseAtomicInteger() {
incccc.getAndIncrement();
}
public static void main(String[] args) {
final test4 test = new test4();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++){
test.increaseSynchronized();
test.increaseVolatile();
test.increaseLock();
test.increaseAtomicInteger();
}
};
}.start();
}
while(Thread.activeCount()>1) //保證前面的執行緒都執行完
Thread.yield();
System.out.println("volatile------"+test.inc);
System.out.println("synchronized-------"+test.incc);
System.out.println("Lock-------"+test.inccc);
System.out.println("AtomicInteger--------"+test.incccc);
/**
* 結論:
* volatile關鍵字可以保證目標的可讀性,但是保證不了目標的原子性
*
* **/
}
}
效果:
volatile關鍵字的第二個特性應用場景:
1.狀態標記量
/**
* volatile關鍵字的應用場景(利用它的有序列性)
* 1.狀態標記量
* 2.double check 雙重檢查
* **/
volatile boolean flag = false;
public void volatileYy(){
while(!flag){
//.....
}
}
public void setFlag() {
flag = true;
}
2.double check 雙重檢查