若是volatile的修飾的是一個引用類型的對象變量,那麼對象中定義的一些普通全局變量是否會受到volatile關鍵字的效果影響呢?」面試
接下來,咱們就一塊兒來分析下這個問題!讓咱們先經過一個例子來回顧下volatile關鍵字的做用!bash
public class VolatitleFoo {
//類變量
final static int max = 5;
static int init_value = 0;
public static void main(String args[]) {
//啓動一個線程,當發現local_value與init_value不一樣時,則輸出init_value被修改的值
new Thread(() -> {
int localValue = init_value;
while (localValue < max) {
if (init_value != localValue) {
System.out.printf("The init_value is update ot [%d]\n", init_value);
//對localValue進行從新賦值
localValue = init_value;
}
}
}, "Reader").start();
//啓動updater線程,主要用於對init_value的修改,當local_value=5的時候退出生命週期
new Thread(() -> {
int localValue = init_value;
while (localValue < max) {
//修改init_value
System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
init_value = localValue;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Updater").start();
}
}
複製代碼
在上面的代碼示例中,咱們定義了兩個類變量max、init_value,而後在主線程中分別啓動一個Reader線程,一個Updater線程。Updater線程作的事情就是在值小於max的值時以每兩毫秒的速度進行自增。而Reader線程則是在感知init_value值發生變化的狀況下進行讀取操做。ui
指望的效果是線程Updater更新init_value值以後,能夠馬上被線程Reader感知到,從而進行輸出顯示。實際運行效果以下:this
The init_value will be changed to [1]
The init_value will be changed to [2]
The init_value will be changed to [3]
The init_value will be changed to [4]
The init_value will be changed to [5]
複製代碼
實際的運行效果是在Updater修改類變量init_value後,Reader線程並無立馬感知到變化,因此沒有進行相應的顯示輸出。而緣由就在於共享類變量init_value在被線程Updater拷貝到該線程的工做內存中後,Updater對變量init_value的修改都是在工做內存中進行的,完成操做後沒有馬上同步回主內存,因此Reader線程對其改變並不可見。spa
爲了解決線程間對類變量init_value的可見性問題,咱們將類變量init_value用volatile關鍵字進行下修飾,以下:線程
static volatile int init_value = 0;
複製代碼
而後咱們再運行下代碼,看看結果:設計
The init_value will be changed to [1]
The init_value is update ot [1]
The init_value will be changed to [2]
The init_value is update ot [2]
The init_value will be changed to [3]
The init_value is update ot [3]
The init_value will be changed to [4]
The init_value is update ot [4]
The init_value will be changed to [5]
The init_value is update ot [5]
複製代碼
此時線程Updater對類變量init_value的修改,立馬就能被Reader線程感知到了,這就是volatile關鍵字的效果,可讓共享變量在線程間實現可見,緣由就在於在JVM的語義層面要求被volatile修飾的共享變量,在工做內存中的修改要馬上同步回主內存,而且讀取也須要每次都從新從主內存中刷新一份到工做內存中後才能夠操做。code
關於以上適用volatile關鍵字修飾基本類型的類變量、實例變量的場景,相信你們會比較好理解。接下來,咱們來繼續改造下代碼:cdn
public class VolatileEntity {
//使用volatile修飾共享資源i
//類變量
final static int max = 5;
int init_value = 0;
public static int getMax() {
return max;
}
public int getInit_value() {
return init_value;
}
public void setInit_value(int init_value) {
this.init_value = init_value;
}
private static class VolatileEntityHolder {
private static VolatileEntity instance = new VolatileEntity();
}
public static VolatileEntity getInstance() {
return VolatileEntityHolder.instance;
}
}
複製代碼
咱們將以前代碼中的類變量init_value放到實體類VolatileEntity中,並將其設計爲一個實例變量,爲了便於理解,咱們將實體類VolatileEntity設計爲單例模式,確保兩個線程操做的是同一個共享堆內存對象。以下:對象
public class VolatileEntityTest {
//使用volatile修飾共享資源
private static VolatileEntity volatileEntity = VolatileEntity.getInstance();
private static final CountDownLatch latch = new CountDownLatch(10);
public static void main(String args[]) throws InterruptedException {
//啓動一個線程,當發現local_value與init_value不一樣時,則輸出init_value被修改的值
new Thread(() -> {
int localValue = volatileEntity.init_value;
while (localValue < VolatileEntity.max) {
if (volatileEntity.init_value != localValue) {
System.out.printf("The init_value is update ot [%d]\n", volatileEntity.init_value);
//對localValue進行從新賦值
localValue = volatileEntity.init_value;
}
}
}, "Reader").start();
//啓動updater線程,主要用於對init_value的修改,當local_value=5的時候退出生命週期
new Thread(() -> {
int localValue = volatileEntity.init_value;
while (localValue < VolatileEntity.max) {
//修改init_value
System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
volatileEntity.init_value = localValue;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Updater").start();
}
}
複製代碼
在上述代碼中線程Updater和Reader此時操做的是類變量VolatileEntity對象中的普通實例變量init_value。在VolatileEntity對象沒被volatile關鍵字修飾以前,咱們看下運行效果:
The init_value will be changed to [1]
The init_value will be changed to [2]
The init_value will be changed to [3]
The init_value will be changed to [4]
The init_value will be changed to [5]
複製代碼
與未被volatile修飾的int類型的類變量效果同樣,線程Updater對VolatileEntity對象中init_value變量的操做也不能立馬被線程Reader可見。若是此時咱們不VolatileEntity類中單獨用volatile關鍵字修飾init_value變量,而是直接VolatileEntity對象用volatile關鍵字修飾,效果會如何呢?
private static volatile VolatileEntity volatileEntity = VolatileEntity.getInstance();
複製代碼
此時VolatileEntity對象的引用變量被volatile關鍵字修飾了,然而其中的普通實例變量init_value並無直接被volatile關鍵字修飾,而後咱們在運行下代碼看看效果:
The init_value will be changed to [1]
The init_value is update ot [1]
The init_value will be changed to [2]
The init_value is update ot [2]
The init_value will be changed to [3]
The init_value is update ot [3]
The init_value will be changed to [4]
The init_value is update ot [4]
The init_value will be changed to [5]
The init_value is update ot [5]
複製代碼
從實際的運行效果上看,雖然咱們沒有直接用volatile關鍵字修飾對象中的類變量init_value,而是修改了對象的引用,可是咱們看到對象中的普通實例變量仍然實行了線程間的可見性,也就是說間接也至關於被volatile關鍵字修飾了。因此,在這裏問題也就基本上有了答案,那就是:「被volatile關鍵字修飾的對象做爲類變量或實例變量時,其對象中攜帶的類變量和實例變量也至關於被volatile關鍵字修飾了」。
這個問題主要是考查你們對volatile關鍵字的理解是否深刻,另外也是對Java數據存儲結構的考查,雖然可能你們對volatile關鍵字的做用會有了解,可是若是忽然被問到這樣的問題,若是不加以思考,在面試中也是很容易被問懵的!