臭名昭著的双重锁检查(也叫多线程单例模式)是一个骗人的把戏,它用来支持lazy初始化,同时避免过度使用同步。在非常早的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;

}

这看起来好像非常聪明——在公用代码中避免了同步。这段代码只有一个问题 —— 它不能正常工作。为什么呢?最明显的原因是,初始化实例的写入操作和实例字段的写入操作能够被编译器或者缓冲区重排序,重排序可能会导致返回部分构造的一些东西。就是我们读取到了一个没有初始化的对象。这段代码还有很多其他的错误,以及为什么对这段代码的算法修正是错误的。在旧的java内存模型下没有办法修复它。更多深入的信息可参见:Double-checkedlocking: Clever but broken and The “DoubleChecked Locking is broken” declaration

许多人认为使用volatile关键字能够消除双重锁检查模式的问题。在1.5的JVM之前,volatile并不能保证这段代码能够正常工作(因环境而定)。在新的内存模型下,实例字段使用volatile可以解决双重锁检查的问题,因为在构造线程来初始化一些东西和读取线程返回它的值之间有happens-before关系。

然后,对于喜欢使用双重锁检查的人来说(我们真的希望没有人这样做),仍然不是好消息。双重锁检查的重点是为了避免过度使用同步导致性能问题。从java1.0开始,不仅同步会有昂贵的性能开销,而且在新的内存模型下,使用volatile的性能开销也有所上升,几乎达到了和同步一样的性能开销。因此,使用双重锁检查来实现单例模式仍然不是一个好的选择。(修订—在大多数平台下,volatile性能开销还是比较低的)。

使用IODH来实现多线程模式下的单例会更易读:

private static class LazySomethingHolder {

public static Something something = new Something();

}

public static Something getInstance() {

return LazySomethingHolder.something;

}

这段代码是正确的,因为初始化是由static字段来保证的。如果一个字段设置在static初始化中,对其他访问这个类的线程来说是是能正确的保证它的可见性的。

原文

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

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 - 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

Many people assumed that the use of the volatilekeyword 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.

However, for fans of double-checked locking (and we really hope there are none left), the news is still not good. The whole point of double-checked locking was to avoid the performance overhead of synchronization. Not only has brief synchronization gotten a LOT less expensive since the Java 1.0 days, but under the new memory model, the performance cost of using volatile goes up, almost to the level of the cost of synchronization. So there’s still no good reason to use double-checked-locking.Redacted — volatiles are cheap on most platforms.

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

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.

java锁上升_Java内存模型FAQ(十一)新的内存模型是否修复了双重锁检查问题?...相关推荐

  1. java 生产者消费者_Java多线程:线程间通信—生产者消费者模型

    一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是, 多个线程之间如何协作呢 ? 我们看一个 ...

  2. java cpu高_Java中的CPU占用高和内存占用高的问题排查

    下面通过模拟实例分析排查Java应用程序CPU和内存占用过高的过程.如果是Java面试,这2个问题在面试过程中出现的概率很高,所以我打算在这里好好总结一下. 1.Java CPU过高的问题排查 举个例 ...

  3. java 原子数据类型_java并发编程(十一)----(JUC原子类)基本类型介绍

    上一节我们说到了基本原子类的简单介绍,这一节我们先来看一下基本类型: AtomicInteger, AtomicLong, AtomicBoolean.AtomicInteger和AtomicLong ...

  4. java多线程构造函数_java线程基础巩固---多线程与JVM内存结构的关系及Thread构造函数StackSize的理解...

    多线程与JVM内存结构的关系[了解]: 对于最后一个有疑问的构造中stackSize参数,其实学过编程滴人从参数字面就比较容易理解,栈大小嘛,这里从官方文档上来了解一下这个参数: 而之前在学习java ...

  5. linux脚本制定java堆大小_Java使用比堆大小更多的内存(或正确的Docker内存限制大小)...

    Java使用比堆大小更多的内存(或正确的Docker内存限制大小) 对于我的应用程序,Java进程使用的内存远远超过堆大小. 容器正在运行的系统开始出现内存问题,因为容器占用的内存比堆大小多得多. 堆 ...

  6. 各种存储分配算法java代码实现_Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF...

    1 概述 本文是利用Java实现操作系统中的四种动态内存分配方式 ,分别是:BF NF WF FF 分两部分,第一部分是介绍四种分配方式的概念以及例子,第二部分是代码实现以及讲解. 2 四种分配方式 ...

  7. java字符串拼接_Java 8中字符串拼接新姿势:StringJoiner

    有一个重要的拼接方式,那就是Java 8中提供的StringJoiner ,本文就来介绍一下这个字符串拼接的新兵. 如果你想知道一共有多少种方法可以进行字符串拼接,教你一个简单的办法,在Intelli ...

  8. linux内存管理(五)-引导内存分配器

    linux内存三大分配器:引导内存分配器,伙伴分配器,slab分配器 一.引导内存分配器 1.引导内存分配器的作用 因为内核里面有很多内存结构体,不可能在静态编译阶段就静态初始化所有的这些内存结构体. ...

  9. Java并发篇_Java内存模型

    在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题.那么它们产生的原因和在Java中解决的办法又是什么呢? 一.内存模型的相关概念 ​ 计算机在执行程序时,每条指令都是在CP ...

最新文章

  1. 如何确定最初克隆本地Git存储库的URL?
  2. java biginteger使用_java中的BigInteger的基本用法 | 学步园
  3. html倒计时timer,JavaScript定时器设置、使用与倒计时案例详解
  4. Java虚拟机JVM的内部体系结构
  5. php 静态 成员属性,[已解决]php中静态成员方法和静态成员变量是不是不支持多态?...
  6. 微信公众号web端关闭本页面
  7. Redis实现分布式锁2
  8. 中英文对照 —— 生活中常见词汇
  9. Shell脚本编程之(三)执行方式差异(source, sh script, ./script)
  10. JCR分区与中科院分区详解-中科院基础版和升级版详解
  11. 小米8青春版解BL锁教程申请BootLoader解锁教程
  12. php汉字转换拼音的类 做了修改用mb_convert_encoding代替iconv实现编码转换
  13. 数字系统实验—第11-12周任务(认识数据存储芯片HM62256、IP核、LPM开发流程和平台、 IIC串行总线时序分析)
  14. 我的世界服务器怎么制作头颅,我的世界怎么刷生物头颅 生物头颅制作方法
  15. python按位置从字符串提取子串的操作是_Python基础-字符串操作和“容器”的操作...
  16. C#操作TMPOS58串口打印机一些心得
  17. 敏捷mini培训总结
  18. 热门在线项目管理工具
  19. 王爽 《汇编语言》之寄存器
  20. 吞吐量、QPS、并发数等概念

热门文章

  1. 桌面上计算机右键管理打不开,计算机右键打不开管理怎么办
  2. 交大慧谷培训python怎样
  3. linux下修改Maven本地厂库地址
  4. 网站卡了?4步方法一秒排查
  5. vue生命周期函数(应用场景要讲到):
  6. 2015年最后一个月的英语小结
  7. video在某些浏览器默认静音
  8. Apple 注销账户 Revoke Token
  9. 2020腾讯暑期游戏客户端实习面经(已OC)
  10. python和C语言混合编程实例