双重检查锁定 - Double checked locking,是一种单例的方式。 其中的有序性意思是指令的执行顺序不会被重新排列。 ,重排序后的结果不受影响,但是就会导致单例失效的问题。 可以认为在实例化的//3语句结束后就释放了锁,因此重排序会导致在mInstance仍然为null的时候第二个线程就进入了。 volatile在阻止了重排序之后,第二个线程只能在 instance正确初始化并不为 null的情况下才能获取锁,所以 volatile避免了DCL被破坏的情况。
双重检查锁的单例 下面是我们经常使用的一种单例的实现,也就是双重检查所的实现方案。 } } } return uniqueSingleton; } } 让我们来看一下这个代码是如何工作的:首先当一个线程发出请求后,会先检查 解决方案 对于上面的问题,有两种解决方案 1,使用 volatile 关键词主要可以保证代码的执行顺序不受 jvm 重排序影响。
一键领取预热专享618元代金券,2核2G云服务器爆品秒杀低至18元!云产品首单低0.8折起,企业用户购买域名1元起…
在 Java 编程语言中,双重检查锁定就是这样的一个绝不应该使用的习语。在本文中,Peter Haggar 介绍了双重检查锁定习语的渊源,开发它的原因和它失效的原因。 这些事实将导致代码失败,原因是双重检查锁定难于跟踪。在本文余下的部分里,我们将详细介绍双重检查锁定习语,从而理解它在何处失效。 ---- 回页首 双重检查锁定 为处理清单 3 中的问题,我们需要对 instance 进行第二次检查。这就是“双重检查锁定”名称的由来。将双重检查锁定习语应用到清单 3 的结果就是清单 4 。 双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。 ---- 回页首 双重检查锁定:获取两个 考虑到当前的双重检查锁定不起作用,我加入了另一个版本的代码,如清单 7 所示,从而防止您刚才看到的无序写入问题。 清单 7.
简介 早前的文章中讨论过服务端性能优化之异步查询转同步,在本文中,将讨论双重检查锁定设计模式。通过简单地事先检查锁定条件,该模式减少了锁定获取的次数,通常可以提高性能。 更进一步,我们想在进入同步块后立即再次执行相同的检查,以保持原子操作: public class DclSingleton { private static volatile DclSingleton 替代方案 即使经过双重检查的锁定可能会加快速度,但它至少存在两个问题: 由于它要求volatile关键字才能正常工作,因此它与Java 1.4及更低版本不兼容 它很冗长,使代码难以阅读 由于这些原因,让我们研究没有这些缺陷的其他方案 在本文时,这被认为是编写单例的最简洁,最安全的方法: public enum EnumSingleton { INSTANCE; // 其他方法 } 总结 总而言之,这篇文章介绍了双重检查的锁定模式 ---- 郑重声明:文章首发于公众号“FunTester”,禁止第三方(腾讯云除外)转载、发表。
后来,提出了一个“聪明”的技巧:双重检查锁定(Double-Checked Locking)。想通过双重检查锁定来降低同步的开销。下面是使用双重检查锁定来实现延迟初始化的实例代码。 /** * 双重检查锁定 * * @author xiaoshu */ public class DoubleCheckedLocking { private static Instance } } } return instance; } } 双重检查锁定看起来似乎很完美,但这是一个错误的优化 问题的根源 前面的双重检查锁定实例代码的第4处(instance = new Instance();)创建了一个对象。这一行代码可以分解为如下的3行伪代码。 解决方案一:基于volatile的解决方案 /** * 安全的双重检查锁定 * * @author xiaoshu */ public class SafeDoubleCheckedLocking
相信大多数同学在面试当中都遇到过手写单例模式的题目,那么如何写一个完美的单例是面试者需要深究的问题,因为一个严谨的单例模式说不定就直接决定了面试结果,今天我们就要来讲讲看似线程安全的双重检查锁单例模式中可能会出现的指令重排问题 ---- 双重检查锁单例模式 乍一看下面单例模式没啥问题,还加了同步锁保证线程安全,从表面上看确实看不出啥问题,当在同一时间多个线程同时执行该单例时就会出现JVM指令重排的问题,从而可能导致某一个线程获取的 假设有A、B两个线程去调用该单例方法,当A线程执行到single = new Single()时,如果编译器和处理器对指令重新排序,指令重排后: //1:分配对象的内存空间 memory = allocate 、lock作用的代码块自然是有序执行的,volatile关键字有效的禁止了指令重排序,实现了程序执行的有序性; 看完volatile关键字的特性之后我们应该就明白了,是volatile关键字禁止了指令重排序从而解决了指令重排的问题 更正后的单例 对比上面单例,下面单例在私有静态变量single前面加了修饰符volatile能够防止JVM指令重排,从而解决了single对象可能出现成员变量未初始化的问题。
双重检查锁定与延迟初始化 在Java 程序中,有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化。此时程序员可能会采用延迟初始化。 因此, 人们想出了一个“聪明”的技巧:双重检查锁定(double-checked locking)。人们想通过双重检查锁定来降低同步的开销。下面是使用双重检查锁定来实现延迟初始化的示例代码: ? 在对象创建好之后,执行getInstance()将不需要获取锁,直接返回已创建好的对象。 双重检查锁定看起来似乎很完美,但这是一个错误的优化! 基于volatile 的双重检查锁定的解决方案 对于前面的基于双重检查锁定来实现延迟初始化的方案(指 DoubleCheckedLocking 示例代码), 我们只需要做一点小的修改(把 instance 通过对比基于 volatile 的双重检查锁定的方案和基于类初始化的方案,我们会发现基于类初始化的方案的实现代码更简洁。
,实现了程序执行的有序性; 双重检查锁定模式 双重检查锁定(Double check locked)模式经常会出现在一些框架源码中,目的是为了延迟初始化变量。 下面来看一个 Spring 中双重检查锁定的例子。 这个方法检查判定两次,并使用锁,所以形象称为双重检查锁定模式。 这个方案缩小锁的范围,减少锁的开销,看起来很完美。然而这个方案有一些问题却很容易被忽略。 虽然重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题。 上面错误双重检查锁定的示例代码中,如果线程 1 获取到锁进入创建对象实例,这个时候发生了指令重排序。 注意,volatile禁止指令重排序在 JDK 5 之后才被修复 使用局部变量优化性能 重新查看 Spring 中双重检查锁定代码。
单例模式双重检查锁模式为什么必须加 volatile? 知道单例模式是一种很常见的设计模型,其目的就是为了避免创建过多的对象,给jvm造成比较大的压力,之前也对单例模型进行了比较详细的描述,详情参考我之前博客:链接 如果要实现一种线程安全的单例模型,一般都会采用双重检查锁模式 Singleton(); } } } return instance; } } 这里,就会有疑问,为什么要双重检查 答:这里分情况,如果不用第一个if判断,在多线程情况下,所有的线程都会进行抢锁,所以其实就是串行执行的;如果不用第二个if判断,因为经过第一个if判断,多个线程都会进来,不过只有一个线程能抢到锁,因为singleton 对象是null,所以会进行new Singleton,这种情况,如果不加第二个if判断,第一个线程创建对象之后,之后线程会继续创建的,所以这种就没做到单例 双重检查的原因知道之后,为什么要加volatile
在看Nacos的源代码时,发现多处都使用了“双重检查锁”的机制,算是非常好的实践案例。这篇文章就着案例来分析一下双重检查锁的使用以及优势所在,目的就是让你的代码格调更加高一个层次。 同时,基于单例模式,讲解一下双重检查锁的演变过程。 Nacos中的实例稍微复杂一下,下面以单例模式中的双重检查锁的演变过程。 双重检查锁 针对上面的问题,就有了双重检查锁,示例如下: public class Singleton { private static Singleton instance; 再回顾一下本文的重点: 阅读Nacos源码,发现双重检查锁的使用; 未加锁单例模式使用,会创建多个对象; 方法上加锁,导致性能下降; 代码内局部加锁,双重判断,既满足线程安全,又满足性能需求; 单例模式特例
本文首发:windCoder.com 关于双重检测锁定,了解过单例的应该不陌生,但也容易写错。这里以单例模式为例一起探索。 为了继续优化,因此人们想出了一个“聪明”的技巧,即双重检查锁定(Double-Checked Locking,简称DCL): public class Singleton { public static Signleton getInstance() { // 3 if(instance == null) { // 4:第一次检查 6:第二次检查 instrance = new Singleton(); // 7:问题根源 } 在此期间,JVM会获取一个锁。这个锁可以同步多个线程对同一个类的初始化。
下面这段很简单的基于双重检查锁定(Double-checked locking)实现的延迟初始化(Lazy initialization)代码,还是让spotbugs找出了问题(感谢spotbugs)。 } } } return filedNames; } 解决方案2 基于线程本地存储TLS(Thread Local Storage)实现双重检查锁定 * @return */ public T get(); } BaseVolatile.java package gu.simplemq; /** * 基于volatile的双重检查锁定实现 } return var; } } BaseTls.java package gu.simplemq; /** * 基于Thread Local Storage的双重检查锁定实现 》 《双重检查锁定失败可能性——参照《The “Double-Checked Locking is Broken” Declaration》》 《Java中Volatile关键字详解》
下面这段很简单的基于双重检查锁定(Double-checked locking)实现的延迟初始化(Lazy initialization)代码,还是让spotbugs找出了问题(感谢spotbugs)。 doGetFieldNames(); } } } return filedNames; } #解决方案2 基于线程本地存储TLS(Thread Local Storage)实现双重检查锁定 实例 * @return */ public T get(); } ##BaseVolatile.java package gu.simplemq; /** * 基于volatile的双重检查锁定实现 } } } return var; } } ##BaseTls.java package gu.simplemq; /** * 基于Thread Local Storage的双重检查锁定实现 》][4] [《双重检查锁定失败可能性——参照《The “Double-Checked Locking is Broken” Declaration》》][5] [《Java中Volatile关键字详解
CP.110: Do not write your own double-checked locking for initialization CP.110:不要自已为初始化编写双重检查锁定代码 Reason 在和RAII模式结合使用的时候,通过使用静态局部变量,可以消除自己为初始化编写双重检查锁定代码的需求。 Example(示例) Example with std::call_once. 有可能检出这种惯用法么?
CP.111: Use a conventional pattern if you really need double-checked locking CP.111:如果真的需要好双重检查锁,使用惯用模式 双重检查锁容易把事情搞杂。 如果你真的需要使用双重检查锁,而不管C++核心准则CP.100:不要使用无锁编程方式,除非绝对必要和C++核心准则CP.110:不要自已为初始化编写双重检查锁定代码中的建议,那么在使用双重检查锁时遵循惯用模式 当非线程安全动作很难发生,而且存在快速的线程安全测试可以用于保证不需要该动作,但是无法保证相反的情况,可以使用没有违背C++核心准则CP.110:不要自已为初始化编写双重检查锁定代码准则的双重检查锁模式 first check thread-safe, see also CP.200: Use volatile only to talk to non-C++ memory volatile的使用没有让第一个检查线程安全
Solution(); 还有一种是懒汉模式,顾名思义,当你需要用它的时候才去实例化对象,如果多个线程同时去实例化对象,那么产生的对象可能不唯一,所以存在线程安全的问题,避免这个线程安全的解决办法是用双检查锁
单例模式有很多种,饿汉式,懒汉式,双检锁,公司里大部分都是选择了双检锁,其中sentinel的ContextUtil源码里就有相关的实现: protected static Context trueEnter context.setOrigin(origin); contextHolder.set(context); } return context; } 上述代码使用了双检锁
简介 双重检测锁定模式是一种设计模式,我们通过首次检测锁定条件而不是实际获得锁从而减少获取锁的开销。 双重检查锁定模式用法通常用于实现执行延迟初始化的单例工厂模式。 但是我们需要非常小心的使用双重检测模式,以避免发送错误。 在多线程环境中,因为重排序的影响,我们可能的到意向不到的结果。 } } } return bookDLC; } } 我们先判断bookDLC是否为空,如果为空,说明需要实例化一个新的对象,这时候我们锁住 BookStatic getBookStatic(){ return bookStatic; } } JVM在类被加载之后和被线程使用之前,会进行静态初始化,而在这个初始化阶段将会获得一个锁,
虽然容易,但里面的坑也有很多,比如双重检验锁模式(double checked locking pattern)真的是线程安全的吗? 起因 在对项目进行PMD静态代码检测时,遇到了这样一个问题 Partially created objects can be returned by the Double Checked Locking 大概意思是,使用双重检验锁模式,可能会返回一个部分初始化的对象。 可能大家有些疑虑,什么是部分初始化的对象,我们下面继续分析 什么是双重检验锁模式 public static Singleton getSingleton() { if (instance == 那B线程使用instance时就可能会出现问题,这就是双重检查锁问题所在。
1 前言 我在上一篇文章聊volatile的时候,埋下了一个问题,在并发情况下单例模式双重检验锁可能会存在的问题,那么本文就来详细分析分析它。 2 浅谈单例模式双重检验锁陷阱 首先看一段代码 public class Test04 { private static Test04 test04; public static Test04 } } //-----输出结果 com.dream.sunny.Test04@3f99bd52 com.dream.sunny.Test04@3f99bd52 true 如上是一段单例模式中的懒汉模式双重检验锁 ,线程B无需获取锁 针对以上情况,是否有解决方案,答案是有的,它问题出现在指令重排,我前面有文章专门提到过这个现象,为了读者方便,我这里简单说明一下指令重排是什么,具体可以查看 "JUC并发编程之Volatile 双重检验锁问题解决方案 回头看下我们出问题的双重检查锁程序,它是满足as-if-serial语义的吗?是的,单线程下它没有任何问题,但是在多线程下,会因为重排序出现问题。
本地专用集群(CDC)是基础设施类产品,将中心化的公有云服务,延伸为可在客户机房落地的近场服务,融合公有云与本地IDC的双重优势,用户可以以本地化的时延和数据安全来使用公有云的丰富能力
扫码关注云+社区
领取腾讯云代金券