美文网首页
Java单例模式

Java单例模式

作者: 18c3ad7caf58 | 来源:发表于2017-04-23 16:17 被阅读24次

    昨天读到了公众号“Import New”的Hi,我们再来聊一聊 Java 的单例 很有收获,在此做个简单的记录。

    1.懒汉式

    1.1简单式

    // Version 1
    public class Single1 {
        private static Single1 instance;
        public static Single1 getInstance() {
            if (instance == null) {
                instance = new Single1();
            }
            return instance;
        }
    }
    
    // Version 1.1
    public class Single1 {
        private static Single1 instance;
        private Single1() {}
        public static Single1 getInstance() {
            if (instance == null) {
                instance = new Single1();
            }
            return instance;
        }
    }
    

    问题:多个线程同时访问,如果有多个线程同时运行到if (instance == null)时,都判断为null,这个时候就不是单例了。
    想法:加上synchronized同步锁

    1.2synchronized版本

    // Version 2 
    public class Single2 {
        private static Single2 instance;
        private Single2() {}
        public static synchronized Single2 getInstance() {
            if (instance == null) {
                instance = new Single2();
            }
            return instance;
        }
    }
    

    问题:给gitInstance方法加锁,虽然会避免了可能会出现的多个实例问题,但是会强制除T1之外的所有线程等待,实际上会对程序的执行效率造成负面影响。
    想法:double-check

    1.3 double-chek版本

    // Version 3 
    public class Single3 {
        private static Single3 instance;
        private Single3() {}
        public static Single3 getInstance() {
            if (instance == null) {
                synchronized (Single3.class) {
                    if (instance == null) {
                        instance = new Single3();
                    }
                }
            }
            return instance;
        }
    }
    

    第一个if (instance == null)是为了解决上一方案中的效率问题
    第二个if (instance == null)是为了防止多个实例
    问题:1、instance = new Single3();非原子操作 2、会受到指令重排的影响。

    原子操作:简单来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。
    例如:赋值操作

    m = 6;
    

    指令重排:简单来说,就是计算机为了提高执行效率,会做的一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。

    下面这段话直接从陈皓的文章(深入浅出单实例SINGLETON设计模式)中复制而来:

    主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

    1. 给 singleton 分配内存

    2. 调用 Singleton 的构造函数来初始化成员变量,形成实例

    3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)
      但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

    再稍微解释一下,就是说,由于有一个『instance已经不为null但是仍没有完成初始化』的中间状态,而这个时候,如果有其他线程刚好运行到第一层if (instance == null)这里,这里读取到的instance已经不为null了,所以就直接把这个中间状态的instance拿去用了,就会产生问题。

    这里的关键在于——线程T1对instance的写操作没有完成,线程T2就执行了读操作。

    1.4 终极版本:volatile

    // Version 4 
    public class Single4 {
        private static volatile Single4 instance;
        private Single4() {}
        public static Single4 getInstance() {
            if (instance == null) {
                synchronized (Single4.class) {
                    if (instance == null) {
                        instance = new Single4();
                    }
                }
            }
            return instance;
        }
    }
    

    volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障(什么是内存屏障?),这样,在它的赋值完成之前,就不用会调用读操作。

    注意:volatile阻止的不是singleton = new Singleton()这句话内部[1-2-3]的指令重排,而是保证了在一个写操作([1-2-3])完成之前,不会调用读操作(if (instance == null))。

    2.饿汉式单例

    饿汉式单例是指:指全局的单例实例在类装载时构建的实现方式。

    2.1 饿汉式单例的实现方式

    //饿汉式实现
    public class SingleB {
        private static final SingleB INSTANCE = new SingleB();
        private SingleB() {}
        public static SingleB getInstance() {
            return INSTANCE;
        }
    }
    

    问题:INSTANCE的初始化是在类加载时进行的,而类的加载是由ClassLoader来做的,所以开发者本来对于它初始化的时机就很难去准确把握。

    3 其他的一些方式

    3.1 Effective Java 1 —— 静态内部类

    // Effective Java 第一版推荐写法
    public class Singleton {
        private static class SingletonHolder {
            private static final Singleton INSTANCE = new Singleton();
        }
        private Singleton (){}
        public static final Singleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    }
    

    3.2 Effective Java 2 —— 枚举

    // Effective Java 第二版推荐写法
    public enum SingleInstance {
        INSTANCE;
        public void fun1() { 
            // do something
        }
    }
     
    // 使用
    SingleInstance.INSTANCE.fun1();
    

    4.可参考的链接

    相关文章

      网友评论

          本文标题:Java单例模式

          本文链接:https://www.haomeiwen.com/subject/pfryzttx.html