Volatile可見性是指:在JMM模型中,所有的線程操作數(shù)據(jù)時,都不能直接操作主內(nèi)存里的數(shù)據(jù),都需要將數(shù)據(jù)復(fù)制一份到線程內(nèi)存中,只能修改線程內(nèi)存中的數(shù)據(jù)(詳細可以查找JMM相關(guān)知識)。然而,在一個線程中修改了某一個變量的值之后,應(yīng)該立即將線程內(nèi)存中修改的值,同步到主內(nèi)存中,并通知其他線程,讓其他線程重新獲取變量值。
1、正確的樣例代碼
1、 首先定義一個數(shù)據(jù)類
這里有兩個變量,一個用volatile修飾,另一個沒有;
public class MyData {
int number1 = 0;
volatile int number2 = 0;
public void add(){
this.number1 = 11;
this.number2 = 22;
}
}
2、 新建一個測試類,內(nèi)容如下,
import java.util.concurrent.TimeUnit;
public class ViewableDemo {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " come in ...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.add();
System.out.println(Thread.currentThread().getName() + " update number value to:" + myData.number2);
},"AA").start();
// 此處,如果循環(huán)判斷的時number1,沒有加volatile修飾,就會一直死循環(huán)下去,
// 但是如果沒有這個循環(huán),等待2s之后直接獲取,則能獲取到線程A改變后的值,因為,主線程,是在線程A修改之后才拿到的數(shù)據(jù);
while(myData.number2 == 0){
// 如果數(shù)字一直沒變就死循環(huán)
}
System.out.println(Thread.currentThread().getName() + " get value " + myData.number2);
}
}
3、 程序最終運行結(jié)果如下:
image
2、錯誤示例
一開始我認為,如果沒有加volatile修飾,那么線程內(nèi)修改變量,對其他線程就是不可見的,于是我寫了如下代碼:
public class VolatileTest {
public static void main(String[] args) throws InterruptedException {
MyData myData = new MyData();
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + " come in");
myData.add();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " finish");
},"thread111").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(myData.number1);
System.out.println(myData.number2);
}
}
我一開始以為的運行結(jié)果,應(yīng)該是number1=0,number2=22,結(jié)果卻是這樣:
image
可見,不論有沒有加volatile修飾,主線程都獲取到了修改后的值,這個是為什么呢?
和正確的驗證代碼對比我們不難發(fā)現(xiàn),正確樣例中,主線程在線程A修改數(shù)字之前,就已經(jīng)拿到了數(shù)據(jù),并且一直占用著,所以線程A修改數(shù)字number1之后,因為沒有volatile修飾,所以一直拿不到修改后的值,導(dǎo)致一直死循環(huán),所以寫了以下代碼驗證:
public class VolatileTest {
public static void main(String[] args) throws InterruptedException {
MyData myData = new MyData();
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + " come in");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.add();
System.out.println(Thread.currentThread().getName() + " change number value");
},"AAA").start();
new Thread(()-> {
while(myData.number1 == 0){
}
System.out.println(Thread.currentThread().getName() + " number2 value is " + myData.number1);
},"BBB").start();
new Thread(()-> {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " number2 value is " + myData.number1);
},"CCC").start();
}
}
程序的運行結(jié)果為:
image
結(jié)果可見:線程BBB,因為在線程A修改之前就拿到了數(shù)據(jù),所以一直認為number1數(shù)字是0,陷入了死循環(huán);而線程C,在線程A修改之后,才去取值,取到的時修改后的值。
3、結(jié)論
沒有volatile修飾的變量,在修改之后也會同步到主內(nèi)存中,但是如果其他線程在此之前已經(jīng)取走了數(shù)據(jù),不會通知其他線程修改,加了volatile后,會在主內(nèi)存數(shù)據(jù)發(fā)生變化之后,通知其他所有線程,來重新拿新數(shù)據(jù)。