zoukankan      html  css  js  c++  java
  • 单例模式到Java内存模型

    先说单例模式:

    经典的单例模式实现:

    饿汉式:

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

    懒汉式:

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

    这两种都是可以安全运行在多线程下的。但是每一个都有点缺点,对于第一种如果这个单例的初始化需要很多内存和时间,我们希望用到时在初始化,没有用到就不初始化。对于第二种我们,其实只需要在第一次初始化时需要避免线程冲突,其他时候都可以直接返回的,而第二种的实现则变成了完全的串行(因为每一个操作都需要获得对象锁),非常大的降低了并发度。

    我们尝试以下改进:

    一个好的方式是DCL(double-checked locking),这种方式的实现如下:

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

    这样看起来是完美的解决方案,确实DCL是一个很好的解决思想,在C下其能很好的运行,但在Java下就会有问题了。这全怪Java的JMM(Java内存模型)。

    在执行到instance = new Singleton()这里时,由于Java内存的“无序写入”,

    可能的执行顺序是这样的:

    mem = allocate();             //Allocate memory for Singleton object.
     instance = mem;               //Note that instance is now non-null, but has not been initialized.
     ctorSingleton(instance);      //Invoke constructor for Singleton passinginstance.

    即为instance分配内存,标记instance不为空,初始化instance。

    这样会导致,一个线程刚标记完,还没有初始化赋值给instance,就释放了锁,然后另一个线程进入锁,判断不为空,释放锁,返回instance,这时显然是错的。这里出现这中错误的原因是instance = new instance();并没有真正的执行完,就释放了锁,我实在不能理解这样设计的原因,但很好的是在JDK1.5之后,已不存在这种问题了DCL这个可以很好的运行。但我们还是有必要继续讨论JDK1.5之前如何实现的。

    可以在instance返回之前加一个步奏,确定其确实初始化了。

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

    这样就很好的解决这个问题了。但是代码量和可阅读性已经陡然上升了,那么有没有更好的方法呢?是有的,利用类加载机制来实现,延迟初始化。

    public class Singleton {
    
        private Singleton() {
    
        }
    
        private static class SingletonHolder {
            private static Singleton instance = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    }

    这里补充一点类加载的知识,类加载分为一个步骤,加载->验证->准备->解析->初始化->使用->卸载。

    Java中对何时初始化一个类有严格的说明,这里涉及到的一个规则是当类的静态域,或静态方法被引用的时候,必须对声明这个静态域或方法的类进行初始化。至于说明时候对类进行加载,这个有两种形式:饿加载(只要有其他类引用了它就加载),懒加载(初始化的时候才加载)。具体JVM对这点的实现不同。

    所以当懒汉式能保证用到的时候才进行初始化,而饿汉试则是在加载时就初始化了。

  • 相关阅读:
    Encryption (hard) CodeForces
    cf 1163D Mysterious Code (字符串, dp)
    AC日记——大整数的因子 openjudge 1.6 13
    AC日记——计算2的N次方 openjudge 1.6 12
    Ac日记——大整数减法 openjudge 1.6 11
    AC日记——大整数加法 openjudge 1.6 10
    AC日记——组合数问题 落谷 P2822 noip2016day2T1
    AC日记——向量点积计算 openjudge 1.6 09
    AC日记——石头剪刀布 openjudge 1.6 08
    AC日记——有趣的跳跃 openjudge 1.6 07
  • 原文地址:https://www.cnblogs.com/chaiwentao/p/4959648.html
Copyright © 2011-2022 走看看