文档章节

【翻译十一】“double-checked locking”的问题

Geeyu
 Geeyu
发布于 2017/03/26 21:41
字数 907
阅读 23
收藏 1

Does the new memory model fix the "double-checked locking" problem?

关于double-checked locking的问题,必读这篇文章《双重检查锁定与延迟初始化》

The (infamous) double-checked locking idiom (also called the multithreaded singleton pattern) is a trick designed to support lazy initialization while avoiding the overhead of synchronization. In very early JVMs, synchronization was slow, and developers were eager to remove it -- perhaps too eager. The double-checked locking idiom looks like this:

Double-checked locking,也叫多线程单例模式,是用来在延迟初始化对象的时候,避免同步的开销,因为在早期的JVM中,同步是很慢的。

    // double-checked-locking - don't do this!
    
    private static Something instance = null;
    
    public Something getInstance() {
      if (instance == null) {
        synchronized (this) {
          if (instance == null)
            //此处,初始化和赋值引用可能重排序
            instance = new Something();
        }
      }
      return instance;
    }

This looks awfully clever -- the synchronization is avoided on the common code path. There's only one problem with it -- it doesn't work. Why not? The most obvious reason is that the writes which initialize instance and the write to the instance field can be reordered by the compiler or the cache, which would have the effect of returning what appears to be a partially constructed Something. The result would be that we read an uninitialized object. There are lots of other reasons why this is wrong, and why algorithmic corrections to it are wrong. There is no way to fix it using the old Java memory model. More in-depth information can be found at Double-checked locking: Clever, but broken and The "Double Checked Locking is broken" declaration

以上是一个double-checked locking,尽管看起来很完美,使用普通代码就避免掉了同步,但其实并不起作用。

原因是,instance = new Something();相当于以下三步

  1. 分配对象的内存空间
  2. 初始化对象
  3. 设置instance指向内存空间

其中,2和3可能重排序。详见文献 Double-checked locking: Clever, but brokenThe "Double Checked Locking is broken" declaration

Many people assumed that the use of the volatile keyword would eliminate the problems that arise when trying to use the double-checked-locking pattern. In JVMs prior to 1.5, volatile would not ensure that it worked (your mileage may vary). Under the new memory model, making the instance field volatile will "fix" the problems with double-checked locking, because then there will be a happens-before relationship between the initialization of the Something by the constructing thread and the return of its value by the thread that reads it.

一些人用volatile来解决使用double-checked-locking单例模式带来的问题,但在1.5之前,volatile也未必有用。但在新内存模型中,给instance加个volatile修饰符则可以解决上述问题,因为JMM设置了一个happen-before关系:构造线程对instance的初始化 happen before 其它线程读取这个对象。

 事实上,volatile是通过禁止上述2和3重排序实现的。

Instead, use the Initialization On Demand Holder idiom, which is thread-safe and a lot easier to understand:

相反的,使用Demand Holder来线程安全的完成初始化,会容易好多,Demand Holder如下

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance() {
  return LazySomethingHolder.something;
}

This code is guaranteed to be correct because of the initialization guarantees for static fields; if a field is set in a static initializer, it is guaranteed to be made visible, correctly, to any thread that accesses that class.

用以上方法来实现单例是正确的,因为类在初始化是静态字段时,对任何线程都是可见的。

What if I'm writing a VM?

You should look at http://gee.cs.oswego.edu/dl/jmm/cookbook.html .

Why should I care?

Why should you care? Concurrency bugs are very difficult to debug. They often don't appear in testing, waiting instead until your program is run under heavy load, and are hard to reproduce and trap. You are much better off spending the extra effort ahead of time to ensure that your program is properly synchronized; while this is not easy, it's a lot easier than trying to debug a badly synchronized application.

我为什么要care?并发bug是很难改的,一般测试不出来,只有在程序负载很大的时候才会出现,而且很难复现。花大精力做好同步,总比改bug好啊~

© 著作权归作者所有

Geeyu
粉丝 3
博文 57
码字总数 30351
作品 0
昌平
私信 提问
【译】可以不要再使用Double-Checked Locking了

Double-Checked Locking方法被广泛的使用于实现多线程环境下单例模式的懒加载方式实现,不幸的是,在JAVA中,这种方式有可能不能够正常工作。在其他语言环境中,如C++,依赖于处理器的内存模...

Float_Luuu
2016/05/03
1K
4
【单例设计模式】单例模式中为什么用枚举更好

枚举单例(Enum Singleton)是实现单例模式的一种新方式,尽管单例模式在java中已经存在很长时间了,但是枚举单例相对来说是一种比较新的概念,枚举这个特性是在Java5才出现的,这篇文章主要...

冷冷gg
2016/08/30
140
0
volatile应用场景之--Double check

推荐两篇文章 https://blog.csdn.net/dl88250/article/details/5439024 http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization 讲的非常好,解释了为啥需要在......

karma123
2018/08/31
13
0
模板引擎 Velocity 1.6.4 发布-下载

在 Velocity 1.7 正式版发布之前发布的 1.6.4 版本主要是为了修复三个高危的bug。 该版本修正了三个问题: 1. 修正了 #parse 中当IncludeEventHandler 返回null时导致的空指针异常 2. Fix d...

红薯
2010/05/19
1K
0
如何正确地写出单例模式

单例模式算是设计模式中最容易理解,也是最容易手写代码的模式了吧。但是其中的坑却不少,所以也常作为面试题来考。本文主要对几种单例写法的整理,并分析其优缺点。很多都是一些老生常谈的问...

Henrykin
2016/12/16
8
0

没有更多内容

加载失败,请刷新页面

加载更多

EDI 电子数据交换全解指南

EDI(Electronic Data Interchange,电子数据交换)技术使得企业与企业(B2B)实现通信自动化,帮助交易伙伴和组织更快更好地完成更多工作,并消除了人工操作带来的错误。从零售商到制造商、物...

EDI知行软件
今天
3
0
CentOS7的LVM动态扩容

# 问题 CentOS7上面的磁盘空间有点紧张,需要扩容。 解决 查询当前磁盘状态 [root@xxx ~]# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTfd0 2:0 1 4K ...

亚林瓜子
今天
3
0
Kafka 0.8 Producer (0.9以前版本适用)

Kafka旧版本producer由scala编写,0.9以后已经废除 示例代码如下: import kafka.producer.KeyedMessage;import kafka.javaapi.producer.Producer;import kafka.producer.ProducerConfig;......

实时计算
今天
5
0
Giraph源码分析(八)—— 统计每个SuperStep中参与计算的顶点数目

作者|白松 目的:科研中,需要分析在每次迭代过程中参与计算的顶点数目,来进一步优化系统。比如,在SSSP的compute()方法最后一行,都会把当前顶点voteToHalt,即变为InActive状态。所以每次...

数澜科技
今天
6
0
Navicat 快捷键

操作 结果 ctrl+q 打开查询窗口 ctrl+/ 注释sql语句 ctrl+shift +/ 解除注释 ctrl+r 运行查询窗口的sql语句 ctrl+shift+r 只运行选中的sql语句 F6 打开一个mysql命令行窗口 ctrl+l 删除一行 ...

低至一折起
今天
13
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部