前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站
首先,有一个静态的布尔变量:
public static boolean flag = false;
然后,开启两个线程。
A线程:
new Thread(() -> {
System.out.println(Thread.currentThread() + "start...");
while(!flag) {
}
System.out.println(Thread.currentThread() + "success...");
}).start() ;
很明显,这是一个死循环。
B线程:
//延迟1秒,确保上面的先执行Thread.sleep(1000);
new Thread(() -> {
flag = true;
System.out.println(Thread.currentThread() + "flag 变为 true了!");
}).start() ;
B线程在1秒后执行,把flag变为true。那么按理说,因为flag是静态属性,它的值发生变化后,A线程应该感应到这种变化,从而跳出死循环。
可是实际上:
Thread[Thread-0,5,main]start...
Thread[Thread-1,5,main]flag 变为 true了!
A线程一直在死循环,并没有发生变化。
为啥会和想的不同?
如图,JMM内存模型中,每一个线程都有一个高速缓冲区,这个区域是每个线程自己独有的内存空间,flag其实在主内存中,每个线程是拷贝了一个flag副本到自己的内存。
因为A线程有死循环,一直在占用着flag副本,也就没有发现该变量已经被其他线程修改了。
解决办法--用volatile关键字
public static volatile boolean flag = false;
当flag被volatile关键字修饰,它的值发生变化时,JVM会立刻将flag重新写入主内存。并且通知其他线程(引用了这个副本的线程)立刻销毁flag变量。
从而迫使这些线程重新从主内存读取flag,这样就可以做到保证多线程下变量的数据一致性了。
实际应用--双重检测锁机制下的对象半实例化问题解决
public class MySingleton {
private static volatile MySingleton mySingleton;
private MySingleton() {}
public static MySingleton newInstance() {
if(mySingleton == null) {
synchronized (mySingleton) {
if(mySingleton == null) {
mySingleton = new MySingleton();
}
}
}
return mySingleton;
}
}
为什么会出现半实例化?
mySingleton = new MySingleton();
这行代码在汇编里面执行的情况下大致是这样的,伪代码如下:
1.mySingleton 分配内存
2.new mySingleton() 实例化
3.mySingleton实例对象赋值给mySingleton
其中,步骤2 和 步骤3 即便颠倒顺序也不会影响最终的结果,所以CPU可能会进行指令重排序。如果步骤3先执行了,而恰好在这个时候别的线程检测到mySingleton不是null,就直接返回了。此时,mySingleton甚至还没有调用构造方法呢,只是一个空的引用(但已经不是null了)。
所以,这就导致了对象半实例化。解决方法也是加上volatile关键字。