1.volatile关键字的作用
- 保证线程之间的可见性。
- 禁止指令重排序
备注:volatile并非原子性,因此并不能保证线程安全
2.volatile可见性
线程之间的可见性:
一个线程修改的状态对另一个线程是可见的。用volatile修饰的变量,就会具有可见性。当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。
测试说明:
在下述程序中,当flag不被volatile关键字修饰时,程序不会结束,及时主线程修改了flag的值为false,但对于t1线程来说是不可见的,因此一直死循环;当flag被volatile关键字修饰时,当主线程修改了flag的值为false,对于t1线程是可见的,当t1线程读取flag为false时,结束循环。
package com.qin.test;
import java.util.concurrent.TimeUnit;
/**
* @author wzb
* @date 2020/10/15 09:58:32
* @desciption volatile可见性测试
*/
public class VolatileTest1 {
//一: 不加volatile,main方法不会结束,一直运行
//boolean flag = true;
//二: 加volatile,main方法中t2线程修改flag的值后,可以终止test方法中的死循环
volatile boolean flag = true;
public void test() {
System.out.println("test method start !");
while (flag) {}
System.out.println("test method end !");
}
public static void main(String[] args) throws InterruptedException {
VolatileTest1 volatileTest=new VolatileTest1();
//t1线程执行test方法
new Thread(volatileTest::test, "t1").start();
TimeUnit.SECONDS.sleep(1);
//主线程修改volatileTest的flag
volatileTest.flag=false;
}
}
3.指令重排序
指令重排序:
即程序运行时,CPU并非绝对按照程序代码的逻辑顺序执行。
测试说明:
在下述程序中,程序运行x,y的结果正常来说只有(1,0),(1,1),(0,1),即程序不会输出(0,0),程序一直运行;但是下述程序最终出现了(0,0)现象,结束了循环;因为CPU内部发生了指令重排序导致,CPU这么做的目的是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。
备注:测试下述程序需要耐心等待…
package com.qin.test;
/**
* @author wzb
* @date 2020/10/15 09:58:32
* @desciption 指令重排序测试
*/
public class VolatileTest2 {
private static int x=0,y=0;
private static int a=0,b=0;
public static void main(String[] args) throws InterruptedException {
int i=0;
for (;;){
i++;
x=0;y=0;
a=0;b=0;
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
a=1;
x=b;
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
b=1;
y=a;
}
});
t1.start();t2.start();
t1.join();t2.join();
System.err.println(i);
if (x==0 && y==0){
System.err.println("循环第"+i+"次(x=0,y=0)");
break;
}
}
}
}