DCL双检锁的失效:现实与初衷的背离

最近看了Brian Goetz写的一篇有关DCL的文章:Double-checked locking: Clever, but broken。( 2001年发表于JavaWorld上)

这篇文章讲述了DCL设计的初衷,但是因为JVM的不同实现(没有严格遵循JMM规范)导致DCL在实际应用中失效。

1. DCL的设计初衷

DCL是为了支持 Lazy initialization而设计的。

我们有多种方式去实现单例模式:

  • Eager initialization: 饿加载,当类加载器加载类时就初始化类变量。这种方式可以保证初始化的原子性,但降低了程序的启动性能(类加载过程中的初始化阶段对类变量初始化)。
  • Lazy initialization: 懒加载,当需要使用类变量时才进行初始化。这种方式可以提高程序的启动性能,但需要同步来保证初始化的原子性,无疑带来了同步开销。

DCL的目标是(1)提高程序的启动性能,(2)降低同步开销。

代码示例:

1 class SomeClass {
2   private Resource resource = null;
3   public Resource getResource() {
4     if (resource == null)
5       resource = new Resource();
6     return resource;
7   }
8 }

2. DCL失效的原因

DCL要真正有效,需要依赖JVM真正遵循了JMM规范。

实际中,编译器、处理器、缓存都可以“自由”执行代码(乱序执行),只要保证as-if-serial semantics就行。

这里要提及synchronized的作用:

  • 互斥执行某段代码
  • 触发memory barrier,强制线程的工作内存与住内存同步

适当地使用同步可以保证:一个线程预期地看到另一个线程的影响(The proper synchronization guarantees that one thread will see the effects of another in a predictable manner)。

DCL之所以失效,是因为没有同步地使用resource。(与上文提到的乱序执行有关,这个与具体的JVM版本有关,很多JVM实现没有严格实现JMM规范)

3. 解决方案

(1)不使用DCL;

(2)使用饿加载(Eager initialization),由类加载器保证类变量初始化的互斥性(每个类只会加载一次);

(3)懒加载有个特例:只含有一个类变量,不含类方法、实例变量、实例方法;

(4)对 32bit primitive values 有效。

原文地址:https://www.cnblogs.com/huangzejun/p/8185220.html

时间: 2024-10-31 14:01:58

DCL双检锁的失效:现实与初衷的背离的相关文章

多线程单利模式之双检锁必要性

static CSingleton* GetInstance() { if( m_pInstance == NULL )              //优化性能,总比锁快 { CAutoLock  lock( &cs );              //防止多线程引起的同步问题 if( m_pInstance == NULL )          //确保该段代码进入单线程模式,開始可靠性推断 { m_pInstance  =  new CSingleton; } } return m_pIns

DCL双检查锁机制实现的线程安全的单例模式

public class MyObject { private volatile static MyObject myObject; private MyObject(){} public static MyObject getInstance(){ try { if(myObject != null){ }else{ Thread.sleep(3000); synchronized ( MyObject.class) { if(myObject == null){ myObject = new

双检查锁失效

有关"双重检查锁定失效"的说明 原文地址 译者:丁一 双重检查锁定(以下称为DCL)已被广泛当做多线程环境下延迟初始化的一种高效手段. 遗憾的是,在Java中,如果没有额外的同步,它并不可靠.在其它语言中,如c++,实现DCL,需要依赖于处理器的内存模型.编译器实行的重排序以及编译器与同步库之间的交互.由于c++没有对这些做出明确规定,很难说DCL是否有效.可以在c++中使用显式的内存屏障来使DCL生效,但Java中并没有这些屏障. 来看下面的代码 01 // Single threa

keepalived双BACKUP加nopreempt失效

keepalived双BACKUP加nopreempt不起作用,两个机器同时拥有vip, 排查几天发现是防火墙问题,啃爹. 打开  vi /etc/sysconfig/iptables 插入一条:-A RH-Firewall-1-INPUT -i eth0 -p 112 -j ACCEPT 保存后, 然后重启防火墙,就恢复正常. 要么把防火墙关掉.

Java 面试知识点解析(二)——高并发编程篇

前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大部分内容参照自这一篇文章,有一些自己补充的,也算是重新学习一下 Java 吧. 前序文章链接: Java 面试知识点解析(一)--基础知识篇 (一)高并发编程基础知识 这里涉及到一些基础的概念,我重新捧起了一下<实战 Java 高并发程序设计>这一本书,感觉到心潮澎湃,这或许就是笔者叙述功底扎实的

C++ 单例模式总结与剖析

目录 C++ 单例模式总结与剖析 一.什么是单例 二.C++单例的实现 2.1 基础要点 2.2 C++ 实现单例的几种方式 2.3 单例的模板 三.何时应该使用或者不使用单例 反对单例的理由 参考文章 C++ 单例模式总结与剖析 单例可能是最常用的简单的一种设计模式,实现方法多样,根据不同的需求有不同的写法; 同时单例也有其局限性,因此有很多人是反对使用单例的.本文对C++ 单例的常见写法进行了一个总结, 包括懒汉式.线程安全.单例模板等: 按照从简单到复杂,最终回归简单的的方式循序渐进地介绍

双重检查锁单例模式为什么要用volatile关键字?

前言 从Java内存模型出发,结合并发编程中的原子性.可见性.有序性三个角度分析volatile所起的作用,并从汇编角度大致说了volatile的原理,说明了该关键字的应用场景:在这补充一点,分析下volatile是怎么在单例模式中避免双检锁出现的问题的. 并发编程的3个条件 1.原子性:要实现原子性方式较多,可用synchronized.lock加锁,AtomicInteger等,但volatile关键字是无法保证原子性的:2.可见性:要实现可见性,也可用synchronized.lock,v

单例模式之双重检测锁

先来看看双重检测锁的实现以及一些简要的说明(本文主要说明双重检测锁带来的线程安全问题): /** * 单例模式之双检锁 * @author ring2 * 懒汉式升级版 */ public class Singleton3 { private static volatile Singleton3 instance; private Singleton3() {} public static Singleton3 getInstance() { //首先判断是否为空 if(instance==nu

Java研发工程师知识点总结

Java研发工程师知识点总结 最近一次更新2017年12月08日 大纲 一.Java基础(语言.集合框架.OOP.设计模式等) 二.Java高级(JavaEE.框架.服务器.工具等) 三.多线程和并发 四.Java虚拟机 五.数据库(Sql.MySQL.Redis等) 六.算法与数据结构 七.计算机网络 八.操作系统(OS基础.Linux等) 九.其他 一.Java基础(语言.集合框架.OOP.设计模式等) 1. HashMap和Hashtable的区别 Hashtable是基于陈旧的Dicti