首先,有一个静态的布尔变量:
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关键字。