若是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关键字的做用会有了解,可是若是忽然被问到这样的问题,若是不加以思考,在面试中也是很容易被问懵的!