千家信息网

Java中如何实现双重检查锁

发表于:2026-11-04 作者:千家信息网编辑
千家信息网最后更新 2026年11月04日,这篇文章主要介绍了Java中如何实现双重检查锁,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。前言在实现单例模式时,如果未考虑多线程的情
千家信息网最后更新 2026年11月04日Java中如何实现双重检查锁

这篇文章主要介绍了Java中如何实现双重检查锁,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

    前言

    在实现单例模式时,如果未考虑多线程的情况,就容易写出下面的错误代码:

    public class Singleton {    private static Singleton uniqueSingleton;    private Singleton() {    }    public Singleton getInstance() {        if (null == uniqueSingleton) {            uniqueSingleton = new Singleton();        }        return uniqueSingleton;    }}

    在多线程的情况下,这样写可能会导致uniqueSingleton有多个实例。比如下面这种情况,考虑有两个线程同时调用getInstance():

    TimeThread AThread B
    T1检查到uniqueSingleton为空
    T2
    检查到uniqueSingleton为空
    T3
    初始化对象A
    T4
    返回对象A
    T5初始化对象B
    T6返回对象B

    可以看到,uniqueSingleton被实例化了两次并且被不同对象持有。完全违背了单例的初衷。

    加锁

    出现这种情况,第一反应就是加锁,如下:

    public class Singleton {    private static Singleton uniqueSingleton;    private Singleton() {    }    public synchronized Singleton getInstance() {        if (null == uniqueSingleton) {            uniqueSingleton = new Singleton();        }        return uniqueSingleton;    }}

    这样虽然解决了问题,但是因为用到了synchronized,会导致很大的性能开销,并且加锁其实只需要在第一次初始化的时候用到,之后的调用都没必要再进行加锁。

    双重检查锁

    双重检查锁(double checked locking)是对上述问题的一种优化。先判断对象是否已经被初始化,再决定要不要加锁。

    错误的双重检查锁

    public class Singleton {    private static Singleton uniqueSingleton;    private Singleton() {    }    public Singleton getInstance() {        if (null == uniqueSingleton) {            synchronized (Singleton.class) {                if (null == uniqueSingleton) {                    uniqueSingleton = new Singleton();   // error                }            }        }        return uniqueSingleton;    }}

    如果这样写,运行顺序就成了:

    • 检查变量是否被初始化(不去获得锁),如果已被初始化则立即返回。

    • 获取锁。

    • 再次检查变量是否已经被初始化,如果还没被初始化就初始化一个对象。

    执行双重检查是因为,如果多个线程同时了通过了第一次检查,并且其中一个线程首先通过了第二次检查并实例化了对象,那么剩余通过了第一次检查的线程就不会再去实例化对象。

    这样,除了初始化的时候会出现加锁的情况,后续的所有调用都会避免加锁而直接返回,解决了性能消耗的问题。

    隐患

    上述写法看似解决了问题,但是有个很大的隐患。实例化对象的那行代码(标记为error的那行),实际上可以分解成以下三个步骤:

    1. 分配内存空间

    2. 初始化对象

    3. 将对象指向刚分配的内存空间

    但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:

    1. 分配内存空间

    2. 将对象指向刚分配的内存空间

    3. 初始化对象

    现在考虑重排序后,两个线程发生了以下调用:

    TimeThread AThread B
    T1检查到uniqueSingleton为空
    T2获取锁
    T3再次检查到uniqueSingleton为空
    T4为uniqueSingleton分配内存空间
    T5将uniqueSingleton指向内存空间
    T6
    检查到uniqueSingleton不为空
    T7
    访问uniqueSingleton(此时对象还未完成初始化)
    T8初始化uniqueSingleton

    在这种情况下,T7时刻线程B对uniqueSingleton的访问,访问的是一个初始化未完成的对象。

    正确的双重检查锁

    public class Singleton {    private volatile static Singleton uniqueSingleton;    private Singleton() {    }    public Singleton getInstance() {        if (null == uniqueSingleton) {            synchronized (Singleton.class) {                if (null == uniqueSingleton) {                    uniqueSingleton = new Singleton();                }            }        }        return uniqueSingleton;    }}

    为了解决上述问题,需要在uniqueSingleton前加入关键字volatile。使用了volatile关键字后,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前。

    至此,双重检查锁就可以完美工作了。

    感谢你能够认真阅读完这篇文章,希望小编分享的"Java中如何实现双重检查锁"这篇文章对大家有帮助,同时也希望大家多多支持,关注行业资讯频道,更多相关知识等着你来学习!

    检查 对象 线程 内存 情况 空间 实例 问题 分配 篇文章 同时 性能 指向 第一次 很大 两个 代码 关键 关键字 再次 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 上海挚品互联网科技有限公司 广州为想互联网科技有限公司 哈尔滨大商圈互联网科技有限公司 互联网是第几次科技革命的成果 互联网科技公司经营范围 紫鲸互联网科技 互联网科技的英文 上海致骋互联网科技 互联网科技公司有哪些 互联网高科技股票 互联网金融监管科技 衡水科技产业互联网平台 青岛中康互联网医疗科技有限公司 互联网时代科技改变生活 互联网科技有哪些公司 互联网公司科技股 花神(南京)互联网科技有限公司 北京品多多互联网科技有限公司 银川新华互联网科技学校招聘 每日互联网科技新闻 新华互联网科技 华服互联网科技怎么样 广州互联网络科技有限公司 互联网巨头投资的硬核科技 互联网教育科技公司vipkid 晓花互联网科技有限公司网址 注册互联网科技公司经营范围大全 华夏恒生科技与互联网科技 互联网科技和法律行业 图游互联网科技
    0