单例模式到Java内存模型

 2019-12-10 15:49  阅读(1023)
文章分类:Java Core

先说单例模式:

经典的单例模式实现:

饿汉式:

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对这点的实现不同。

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

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 单例模式到Java内存模型

相关推荐