(1)指令乱排(指令乱排的好处是可以提高处理的速度,但同时可能引发安全性问题),例如下面的案例:
public class InorderCode {
//指令重排的代码验证
private static int a = 0, b = 0;
private static int x = 0, y = 0;
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
for (;;) {
Thread t1 = new Thread(() -> {
a = 1;
x = b;//此处不让x = a的目的是防止存在数据依赖关系导致无法进行指令重排的
});
Thread t2 = new Thread(() -> {
b = 1;
y = a;
});
t1.start();
t2.start();
t1.join();//阻塞main方法,不让主线程提前结束掉
t2.join();//阻塞main方法
count++;
System.out.println("一共执行了" + count + "次!");
if (x == 0 && y == 0) {
System.out.println("一共执行的次数为:" + count + ",X=" + x + ",Y=" + y);
break;
}
a = 0;
b = 0;
x = 0;
y = 0;
}
System.out.println("main方法终止了!————————————————————————————————--------");
/**
* 按照我们正常的情况下,程序顺序执行的逻辑思维加上操作系统的调度问题,x=1,y = 0;或者x=0,y=1;这两种情况都会出现;
* 但是按理说是不可能出现x=1,y=1;或者x=0,y =0的情况,但是在实际的情况下,的确的存在了这样的问题产生;
* 这就说明了指令乱排的真实存在的问题,这就导致使用多线程需要主义的事项之一:指令乱排导致的安全性问题;
*/
}
}
解决的方法是使用内存屏障来解决:
java中使用volatile保证变量在读写时候避免指令重排;在OS中相当于内存屏障,其含义就是相当于在执行接下来的指令之前增加屏障保证不会接着被执行;
对象的版半初始化状态:
背景:比如创建对象时的一些步骤:new invokespecial ldc ;但是由于步骤2,3可以重排,所以指令也可以重排,但是为了避免重排可以使用volatile修饰一下;
所以对象的半初始化状态:就是比如先分配好了内存空间,付了初始值为0,接着也存在了引用;但是实际的构造方法还没有完全执行完毕,此时如果正好使用了这个引用,那么得到只是null就可能导致空指针异常。为了避免就使用volatile;