Volatile可见性是指:在JMM模型中,所有的线程操作数据时,都不能直接操作主内存里的数据,都需要将数据复制一份到线程内存中,只能修改线程内存中的数据(详细可以查找JMM相关知识)。然而,在一个线程中修改了某一个变量的值之后,应该立即将线程内存中修改的值,同步到主内存中,并通知其他线程,让其他线程重新获取变量值。
1、正确的样例代码
1、 首先定义一个数据类
这里有两个变量,一个用volatile修饰,另一个没有;
public class MyData {
int number1 = 0;
volatile int number2 = 0;
public void add(){
this.number1 = 11;
this.number2 = 22;
}
}
2、 新建一个测试类,内容如下,
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();
// 此处,如果循环判断的时number1,没有加volatile修饰,就会一直死循环下去,
// 但是如果没有这个循环,等待2s之后直接获取,则能获取到线程A改变后的值,因为,主线程,是在线程A修改之后才拿到的数据;
while(myData.number2 == 0){
// 如果数字一直没变就死循环
}
System.out.println(Thread.currentThread().getName() + " get value " + myData.number2);
}
}
3、 程序最终运行结果如下:
2、错误示例
一开始我认为,如果没有加volatile修饰,那么线程内修改变量,对其他线程就是不可见的,于是我写了如下代码:
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);
}
}
我一开始以为的运行结果,应该是number1=0,number2=22,结果却是这样:
可见,不论有没有加volatile修饰,主线程都获取到了修改后的值,这个是为什么呢?
和正确的验证代码对比我们不难发现,正确样例中,主线程在线程A修改数字之前,就已经拿到了数据,并且一直占用着,所以线程A修改数字number1之后,因为没有volatile修饰,所以一直拿不到修改后的值,导致一直死循环,所以写了以下代码验证:
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();
}
}
程序的运行结果为:
结果可见:线程BBB,因为在线程A修改之前就拿到了数据,所以一直认为number1数字是0,陷入了死循环;而线程C,在线程A修改之后,才去取值,取到的时修改后的值。
3、结论
没有volatile修饰的变量,在修改之后也会同步到主内存中,但是如果其他线程在此之前已经取走了数据,不会通知其他线程修改,加了volatile后,会在主内存数据发生变化之后,通知其他所有线程,来重新拿新数据。