Java设计模式:单例模式

单例模式的作用:

保证一个类仅有一个实例,并向整个系统提供唯一访问其 实例对象 的方式。主要用于解决一个全局使用的类被频繁地创建和销毁造成资源的浪费问题。

  • 优点:减少了内存的开销,避免对内存资源的多重占用。
  • 缺点:不适用于变化的对象,单例类的复用职责 违背  单一职责原则。

应用场景:网站计数器,数据库连接池,线程池等

 

单例模式构建方式:

  • 饿汉模式:在类加载时实例化对象,优点:线程安全,缺点:资源开销大(加载慢)
  • 懒汉模式:不在类加载时实例化对象,在需要使用的时候才创建实例,优点:加载快,缺点:线程不安全

 

 

1.饿汉模式单例实现:

public class Singleton {

    private Singleton(){}

    private static Singleton singleton = new Singleton();

    public static Singleton getInstance(){
        return singleton;
    }

}

优点:不存在线程安全问题。 缺点:每次调用都实例化,占用空间 。

 

2.懒汉模式单例实现(演进分析)

懒汉模式单例实现①

public class Singleton {
    private static Singleton singleton = null;

    private Singleton(){}

    public static Singleton getInstance() {
        //创建实例前判断实例对象是否为空,防止重复创建对象
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

该实现方式在单线程环境下无问题,但在高并发的情况下,singleton实例未初始化时,容易存在两个或多个线程判断singleton为空,多个线程同时实例化singleton的情况。

 

懒汉模式单例实现②——synchronized方法保证同步

public class Singleton {
    private static Singleton singleton;

    private Singleton(){}
    
    //加入synchronized关键字
    public static synchronized Singleton getInstance(){
        if (singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

该实现虽然简单地解决了多线程竞争的问题,但是如果程序对getInstance方法调用频率很高的话,synchronized锁住整个方法,加之,后续多次进行singleton==null的判断毫无必要,且该非空判断又在synchronize方法内,程序的性能将会大打折扣。

 

懒汉模式单例实现③——DCL(Double Check Lock,双重检查锁)

public class Singleton {
    private static Singleton singleton;

    private Singleton(){}
    
    public static Singleton getInstance(){
        /**
        *先判断singleton是否实例化,若已实例化则返回singleton实例,
        *若无实例化则在同步方法内实例化singleton
        */
        if (singleton != null){
            return singleton;
        }   
        synchronized(Singleton.class){
            /**
            *该if语句防止在线程A实例化singleton时,有另外的线程B执行最外层的if语句,
            *线程A实例化完成singleton后,线程B又进入同步方法实例化singleton的情况
            */
            if(singleton == null){
                singleton = new Singleton();
            }
        }
    }
}

该实现很好地解决了 懒汉单例模式②  中存在的问题,但是singleton = new Singleton()中,由于JVM指令重排序,无法保证该语句是先引用singleton指向堆内存空间(新对象分配的空间),还是先实例化对象(堆内存装配)。问题在于,若是在A线程中,JVM先实例化singleton对象,后引用singleton指向堆内存空间,该情景下当线程A实例化singleton对象(new singleton())时,引用变量singleton仍然指向null,若线程B调用getInstance时判断singleton为null,则返回singleton实例,则引发异常。

 

懒汉模式单例实现④——volatile关键字配合DCL

public class Singleton {
    //volatile关键字
    private volatile static Singleton singleton;

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

volatile关键字保证singleton的可见性以及防止重排序,并且volatile不会造成线程阻塞的问题。

 

3.Lazy initialization holder class模式

public class Singleton {
    /**
    *类级内部类,只有第一次被使用,才会被创建对象,因此在类初始化时不会初始化对象
    *实现了延迟加载
    */
    private static class SingletonHolder {
         /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static Singleton instance = new Singleton();
    }

    private Singleton(){}

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

JVM隐式执行同步的情况:

  1. 静态初始化器(在静态字段上或 static{} 块中的初始化器)
  2. 初始化数据时 
  3. 访问 final 字段时
  4. 在创建线程前创建对象时 
  5. 线程可以看见它将要处理的对象时(volatile关键字)

 

 

总结:

需要对静态域使用延迟初始化加载,建议使用Lazy initialization holder class模式

需要对实例域使用延迟初始化加载,建议使用DCL模式

你可能感兴趣的:(设计模式)