设计模式之单例模式

2020-07-23  本文已影响0人  Mr靖哥哥

一、概念

JVM中,单例对象只有一个实例存在。

二、饿汉式实现

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

最简单的实现方式,但是如果对象的构造耗费时间,可能采用懒汉式更好。

三、懒汉式实现一

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

也是很简单粗暴的懒汉式实现方式,每次获取单例的时候都需要获取排他锁,效率差。

四、懒汉式实现二

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

1、双重判断

2、指令重排

指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。
也就是说,JVM为了执行效率会将指令进行重新排序,但是这种重新排序不会对单线程程序产生影响。

由于instance = new Singleton();操作并不是一个原子性指令,会被分为多个指令:

memory = allocate();  //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory;    //3:设置instance指向刚分配的内存地址

但是经过重排序后如下:

memory = allocate();  //1:分配对象的内存空间
instance = memory;    //3:设置instance指向刚分配的内存地址,此时对象还没被初始化
ctorInstance(memory); //2:初始化对象

3、可见性

instance需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间(空白内存)并将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

五、懒汉式实现三

public class Singleton {
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();

        private SingletonHolder(){
        }
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

1、加载时机

内部静态类是要在有引用了以后才会装载到内存的,所以在你第一次调用getInstance()之前,SingletonHolder是没有被装载进来的,只有在你第一次调用了getInstance()之后,里面涉及到了return SingletonHolder.instance; 产生了对SingletonHolder的引用,内部静态类的实例才会真正装载。这也就是懒加载的意思。

遇到new、getstatic、putstatic、或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化,生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

2、线程安全

虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,只到活动线程执行<clinit>()方法完毕。

上一篇 下一篇

猜你喜欢

热点阅读