首先,每个线程都有自己的工作内存(每个线程私有),而执行任务时,要先从主内存(公共部分)读取要操作的变量,拷贝到自己的工作内存当中,然后对变量进行操作,操作完成后要将变量重新写入主内存当中。
我们可以看到每个线程操作变量的执行流程大概是上图所示的样子
那么我们来进行代码实践:
class MyData{
int number = 0;
public void addT060(){
this.number = 60;
}
}
/**
* 1.验证volatile的可见性
* 1.1 假如int number =0; number变量之前根本没有添加volatile关键字修饰,没有可见性
*/
public class VolatileDemo {
public static void main(String[] args) { //main是一切方法的运行入口
MyData myData = new MyData(); //资源类
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t come in");
//暂停一会儿线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addT060();
System.out.println(Thread.currentThread().getName() + "\t updated number value" + myData.number);
},"AAA").start();
//第二个线程就是我们的main线程
while (myData.number == 0){
//main线程就一直在这里等待循环,直到number值不再等于零
}
System.out.println(Thread.currentThread().getName() + "\t mission is over,main get number value:"+myData.number);
}
}
如果没有加volatile进行修饰变量那么main线程会一直处于等待状态如下图:
而加了volatile后的代码:
class MyData{
volatile int number = 0;
public void addT060(){
this.number = 60;
}
}
/**
* 1.验证volatile的可见性
* 1.1 假如int number =0; number变量之前根本没有添加volatile关键字修饰,没有可见性
*/
public class VolatileDemo {
public static void main(String[] args) { //main是一切方法的运行入口
MyData myData = new MyData(); //资源类
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t come in");
//暂停一会儿线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addT060();
System.out.println(Thread.currentThread().getName() + "\t updated number value" + myData.number);
},"AAA").start();
//第二个线程就是我们的main线程
while (myData.number == 0){
//main线程就一直在这里等待循环,直到number值不再等于零
}
System.out.println(Thread.currentThread().getName() + "\t mission is over,main get number value:"+myData.number);
}
}
在此处我们可以查看以下输出结果:
结论:上图我们看到两个线程读取完数据后,没加volatile时,线程AAA将数据写回主内存区域后,main线程看不到变量number的变化,于是会一直处于循环状态,但加了volatile关键字修饰后,则线程AAA修改number变量后会通知其他线程可见。