从双重校验锁进一步理解synchronized和volatile

并发编程中的四个问题:
可见性、原子性、有序性、指令重排
对于synchronizedvolatile首先我们知道:
synchronized可以保证原子性、有序性、可见性;
volatile只能保证有序性和可见性,但是可以防止指令重排;
那这几个概念是什么意思呢?

什么是可见性?

问题:并发编程时,当一个线程对共享变量进行了修改,另外的线程并没能立即看到最新的值。
原因:本地内存缓存会保存数据副本,这就造成一个线程修改内存的变量后,其他的线程可能由于读取的是本地内存的缓存而造成了数据不一致。(就好像这个变量的改变其他线程看不到一样)
image
解决思路:对于这种变量,应该每次都去内存中读,而不是走缓存;volatile和synchronized都可以解决这个问题。

什么是原子性?

问题:对于num++这个语句,实际上分为三步执行;
1.获取num的值;
2.计算加一之后的值;
3.将新值赋给num;
在多线程中,这三步应该看作一个整体一块执行完,中间不应让另外的线程也执行这三个语句;否者就可能出现问题:
解决思路:在一个线程执行完该代码块之前,其他线程不能执行该代码块;synchronized可以解决这个问题。

什么是有序性?

保证线程串行地执行某个代码块,volatile和synchronized都可以解决这个问题。

什么是防止指令重排?

为了提升执行速度,计算机再执行代码的时候会对指令进行重新排序,并保证达到的最终效果是一样的(但是,但是,它只能保证在单线程没啥问题,当使用多线程时遇上指令重拍就可能出现问题了)
案例:双重校验锁(未禁止指令重排时)

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {
    }

    public  static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

对于uniqueInstance = new Singleton();语句其实是分三步走的:

  1. uniqueInstance分配内存空间
  2. 初始化uniqueInstance
  3. uniqueInstance指向分配的内存地址

但是JVM进行指令重排后,执行过程可能就变成了1->3->2;在多线程环境下,就可能导致一个线程获得还没有初始化的实例。比如线程A执行了1和3,此时T2调用getUniqueInstance后发现uniqueInstance不为null,因此直接返回uniqueInstance给其他程序使用,但实际上uniqueInstance还没初始化呢,如果用的话肯定有问题。
解决:volatile关键字可以禁止指令重排,因此双重校验锁正确的写法应该是:

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public  static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

一些对比

关于volatilesynchronized的有序性需要说明一下,synchronized保证的是代码块之间的有序性,确保多个线程串行地执行代码块,而对于代码块内部的有序是不保证的(也就是不会禁止指令重排);

总结:
volatile:可见性、有序性、禁止指令重排
synchronized:原子性、可见性、有序性

posted @   诗如沿海  阅读(308)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示