zoukankan      html  css  js  c++  java
  • Double-checked Locking (DCL) and how to fix it

    The term double-checked locking (and sometimes the initials DCL) is used to refer to an infamous programming "pattern" which attempts to avoid synchronization when reading the reference to a singeton instance that is constructed through lazy initialisation (that is, it is not constructed until it is first required). An implemenation using synchronization would be:

    public class MyFactory {
      private static MyFactory instance;
    
      public synchronized static MyFactory getFactory() {
        if (instance == null)
          instance = new MyFactory();
        return instance;
      }
    }
    

    The above pattern is perfectly correct. However, some programmers have been reluctant to use it because on all reads it makes a synchronized access and "synchronization is slow". Spurred by this general fear of "slow synchronization", a popular idiom was to attempt to avoid the synchronization on the read as follows:

    public class MyBrokenFactory {
      private static MyFactory instance;
      private int field1, field2 ...
    
      public static MyBrokenFactory getFactory() {
        // This is incorrect: don't do it at home, kids!
        if (instance == null) {
          synchronized (MyBrokenFactory.class) {
            if (instance == null)
              instance = new MyBrokenFactory();
          }
        }
        return instance;
      }
    
      private MyBrokenFactory() {
        field1 = ...
        field2 = ...
      }
    }
    

    On modern JVMs, this optimisation– even if it were correct– is probably no longer that useful. Unfortunately, it's also incorrect. We can show it's incorrect by considering what might happen by two threads that are concurrently calling MyBrokenFactory.getFactory():

    Thread 1: 'gets in first' and starts creating instance.

    Thread 2: gets in just as Thread 1 has written the object reference to memory, but before it has written all the fields.

    1. Is instance null? Yes.
    2. Synchronize on class.
    3. Memory is allocated for instance.
    4. Pointer to memory saved into instance.
    
    
    
    
    
    7. Values for field1 and field2 are written
    to memory allocated for object.
    
    5. Is instance null? No.
    6. instance is non-null, but field1 and
    field2 haven't yet been set! 
    This thread sees invalid values
    for field1 and field2!
    
    
    

    How to fix double-checked locking?

    1. Just use synchronization, stoopid...

    It sounds a bit glib, but one option is to just go ahead and use the synchronization that double-checked locking was trying to avoid. As Goetz et al point out, double-checked locking is "an idiom whose utility has largely passed". In other words, the notion that we must "avoid synchronization because it's slow" just isn't the case any more for uncontended synchronization on modern JVMs. So in effect, we could 'revert back' to the synchronized method that we were trying to avoid:

    public class MyFactory {
      private static MyFactory instance;
    
      public static synchronized MyFactory getInstance() {
        if (instance == null)
          instance = new MyFactory();
        return instance;
      }
    
      private MyFactory() {}
    }
    

    2. Use the class loader

    One of the most optimised and sophisticated pieces of synchronization in the JVM is actually that of the class loader. Every time we refer to a class, it must handle checking whether or not the class is loaded yet, and if it isn't, it must atomically load and initialise it. Every class can have a piece of static class initialisation code. So we could write this:

    public class MyFactory {
      private static final MyFactory instance = new MyFactory();
    
      public static MyFactory getInstance() {
        return instance;
      }
    
      private MyFactory() {}
    }
    

    or, if we wanted or needed to handle exceptions in some special way, we can (and indeed must) define an explicit static block:

    public class MyFactory {
      private static final MyFactory instance;
    
      static {
        try {
          instance = new MyFactory();
        } catch (IOException e) {
          throw new RuntimeException("Darn, an error's occurred!", e);
        }
      }
    
      public static MyFactory getInstance() {
        return instance;
      }
    
      private MyFactory() throws IOException {
        // read configuration files...
      }
    }
    

    Note that if you use the first variant, it actually gets turned by the compiler into something resembling the second. (I've heard people say things like "I don't like static initialisers because of (spurious reason X)", but there is no "magic" place to put variable initialisation in cases such as this!) However it is created, the JVM ensures that this static initialisation code is called exactly once, when the class is first referred to and loaded.

    Using the class loader is generally my preferred way of dealing with lazy static initialisation. The code's nice and simple, and I don't think you can actually do it any more efficiently. The time that it won't work of course is if for some reason you need to pass in a parameter to getInstance().

    3. Use DCL plus volatile

    Double-checked locking is actually OK as of Java 5 provided that you make the instance reference volatile. So for example, if we needed to pass in a database connection to getInstance(), then this would be OK:

    public class MyFactory {
      private static volatile MyFactory instance;
    
      public static MyFactory getInstance(Connection conn)
           throws IOException {
        if (instance == null) {
          synchronized (MyFactory.class) {
            if (instance == null)
              instance = new MyFactory(conn);
          }
        }
        return instance;  
      }
    
      private MyFactory(Connection conn) throws IOException {
        // init factory using the database connection passed in
      }
    }
    

    Note that this is OK as of Java 5 because the definition of volatile was specifically changed to make it OK. Accessing a volatile variable has the semantics of synchronization as of Java 5. In other words Java 5 ensures that the unsycnrhonized volatile read must happen after the write has taken place, and the reading thread will see the correct values of all fields on MyFactory.

    4. Make all fields on the factory final

    In Java 5, a change was made to the definition of final fields. Where the values of these fields are set in the constructor, the JVM ensures that these values are committed to main memory before the object reference itself. In other words, another thread that can "see" the object cannot ever see uninitialised values of its final fields. In that case, we wouldn't actually need to declare the instance reference as volatile.

    http://www.javamex.com/tutorials/double_checked_locking.shtml

  • 相关阅读:
    在k8s上部署第一个php应用
    在k8s中的基本概念
    kubernetes 环境搭建
    docker搭建私有仓库
    mysql导入数据乱码的解决
    代码单词
    让代码更容易读
    docker中的link
    docker基本
    解决无法将“babel”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
  • 原文地址:https://www.cnblogs.com/xzs603/p/3601498.html
Copyright © 2011-2022 走看看