zoukankan      html  css  js  c++  java
  • 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 有效。

  • 相关阅读:
    [Leetcode] Two Sum
    [Leetcode] 4Sum
    [Leetcode] Word Break
    [Leetcode] Evaluate Reverse Polish Notation
    [Leetcode] Distinct Subsequences
    [Leetcode] Triangle
    [Leetcode] Single Number II
    [Leetcode] Convert Sorted Array to Binary Search Tree
    一起手写吧!Promise!
    一起手写吧!sleep函数!
  • 原文地址:https://www.cnblogs.com/huangzejun/p/8185220.html
Copyright © 2011-2022 走看看