我们呢都知道volatile能够防止指令重排序和做到内存可见性。然后synchronized是有原子性。但是具体一个代码能否分析出不用synchronized或者volatile的问题会出现在哪里呢? 接下来我们就来做这样的事情。 现在需要一个synchronized和volatile同时存在的代码,那么我们拿到单例模式的懒汉模式里的双检查加锁:
public class first {
private static volatile Object object;
public Object getSingleton(){
if(object == null){
synchronized (this){
if(object == null){
object = new Object();
}
}
}
return object;
}
}
缺少synchronized
那么我们现在将synchronized代码块删除:
public class first {
private static volatile Object object;
public Object getSingleton(){
if(object == null){
if(object == null){
object = new Object();
}
}
return object;
}
}
感觉可能线程安全是没啥问题的,我们实际用多线程运行一下看看:
public class Main {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 1000, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
Runnable r = ()->{
Object singleton = new first().getSingleton();
System.out.println(singleton);
};
executor.execute(r);
executor.execute(r);
}
}
经过多次运行: 发现这两个线程打印出的对象是不一样的,那么线程安全问题就出现了。
总结
仔细分析一下是为什么呢,这两个线程在进入getSingleton() 方法后,第一个线程可能到达了 object = new Object(); ,但还没执行这一步,第二个线程碰巧也到了同一位置,当第一个线程执行了创建然后返回对象1,打印了对象1,第二个线程开始执行,创建了对象2,返回并打印,所以这两个线程打印的内容是不一样的。
缺少 volatile
当我们把修饰变量的volatile删除掉,
public class first {
private static Object object;
public Object getSingleton(){
if(object == null){
synchronized (this){
if(object == null){
object = new Object();
}
}
}
return object;
}
}
此时我们再次运行 发现还是出现了线程安全问题,这次问题是出在哪里呢
总结
缺少了volatile,那么防止指令重排序的作用就没有了。 对于object = new Object(); 这一条命令,它是分成三步,
- 分配内存空间
- 初始化对象
- 将对象指向刚分配的内存空间
经过指令重排序,可能将2和3的步骤交换,此时,线程1如果将对象指向了内存空间但未初始化,线程2执行第一个判断判断出非null了就直接返回了这个未初始化成功的对象。
总结
volatile能实现变量的修改可见性和防止指令重排序,不能保证原子性;而synchronized则可以保证原子性和修改可见性,但是不能防止指令重排序
|