• 【Singleton】单例模式、双检测锁定DCL、volatile(二) ctd and how to fix it (ctd)


    On the previous page, we looked at why double-checked locking is a problem. Now we look at correct idioms that can be used instead.

    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.

    -----------------------------------volatile介绍------------------------------------------
    java的volatile是什么意思
    我们知道,在Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步。

    这在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变得非常重要。

    在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。

    要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。

    Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

    Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。

    这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

    而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

    使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。

    由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

    注:我在单例模式中用到此关键字.

    $$ 单例的一个例子:

    public class Singleton(){
         private volatile static Singleton singleton;
         private Sington(){};
         public static Singleton getInstance(){
         if(singleton == null){
             synchronized (Singleton.class);
                  if(singleton == null){
                     singleton = new Singleton();
                                       }
                              }
                  }
    return singleton;
            }
    }

    参考:

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

    http://crud0906.iteye.com/blog/576321

  • 相关阅读:
    kubernetes获取Pod内容器信息
    etcd空间配额2G限制优化
    kubernetes集群之GC处理
    kubernetes之statefulset控制器介绍
    基于MySQL Binlog的Elasticsearch数据同步实践
    Nacos
    Python最佳工程实践,建立一个完美的工程项目
    图数据库的内部结构 (NEO4j)
    5个用/不用GraphQL的理由
    Neo4J 查找两节点之间的路径
  • 原文地址:https://www.cnblogs.com/549294286/p/3031478.html
Copyright © 2020-2023  润新知