设计模式:单例模式

馋奶兔 提交于 2019-12-03 14:44:42

单例模式可以保证一个类只允许存在一个该列的实例,并提供了访问该类对象的唯一方式。
在许多应用场景中,该模式可以保证系统的正确运行。

以下是单例模式的几种实现方式。

方式一:最基础的单例模式

public class ThreadUnsafeSingleton {

    private static ThreadUnsafeSingleton singleton;

    //私有化构造函数 禁止外部调用
    private ThreadUnsafeSingleton(){

    }

    //通过静态方法获取对象
    public static ThreadUnsafeSingleton getSingleton(){
        //当两个线程同时进入if判断时,每个线程都能够创建singleton对象
        if(singleton == null)
            singleton = new ThreadUnsafeSingleton();
        return singleton;
    }
}

上面的代码中先通过私有化类的构造函数,禁止外部直接调用构造函数创建对象,同时增加了一个私有的静态变量存放单例对象,并提供出一个静态的方法,用于对外提供单例对象的获取方式。

该实现方法无法在多线程环境下正确的运行,假设同时有两条Thread1Thread2正在执行if(signleton == null)的判断,且此时还没有实例被创建,那么接下来两条线程都会创建singleton对象,而无法确保单例对象的唯一性。

方式二:通过同步确保线程安全:

既然方式一的缺陷是当两条线程在同时执行getSingleton时存在线程安全问题,那么最简单的解决方案就是通过同步保证getSingleton的线程安全。

public class SynchronizedSingleton {

    private static SynchronizedSingleton singleton;

    private SynchronizedSingleton(){

    }

    //通过同步的方式保证多线程下的安全性
    //但是每次获取对象都需要加锁和释放锁,影响性能
    public static synchronized SynchronizedSingleton getSingleton(){
        if(singleton == null)
            singleton = new SynchronizedSingleton();
        return singleton;
    }
}

这种方式的缺点时每次想要获取对象时,都需要上锁在释放锁,影响性能。

方式三:双重锁模式

方式一的关键问题是在于当同时多个线程在判断是否已经创建过单例对象时,会存在线程安全问题,而针对已经确认单例对象被创建的情况下,不存在线程安全问题,那么我们可以考虑缩小同步的范围,以此提升效率。

public class DoubleCheckSingleton {

    //避免JVM重排序造成的影响
    private static volatile DoubleCheckSingleton singleton;

    private DoubleCheckSingleton(){

    }

    public static DoubleCheckSingleton getSingleton(){
        if(singleton == null){
            //只针对关键的部分加锁,在已经创建过singleton后,再获取singleton时并不需要在上锁
            synchronized (DoubleCheckSingleton.class){
                //为了避免误判断,需要在同步块内再检查一次
                if(singleton == null)
                    singleton = new DoubleCheckSingleton();
            }
        }
        return singleton;
    }
}

为了保证判断对象是否被创建的结果有效,必须在同步块内再增加一次判断。并且为了避免JVM重排序造成的影响,需要将私有静态对象用violate标记。

方式四:静态初始化

保证线程最简单的方式是利用JVM加载类时的机制。因此可以考虑把对象创建的过程放在类被加载时。

public class StaticSingleton {

    //通过类加载的机制保证线程的安全性
    //但无法做到惰性加载,可能会影响启动时间和内存使用
    private static StaticSingleton singleton = new StaticSingleton();

    private StaticSingleton(){

    }

    public static StaticSingleton getSingleton(){
        return singleton;
    }
}

这种做法的缺点时无法做到惰性加载,类被加载时就创建了对象,假设这个对象创建费时或是对象结构很大,可能造成启动变慢或是内存浪费的现象。

方式五:静态内部类

我们可以考虑将单例对象交给静态内部类来持有,通过控制加载静态内部类的时机来到达懒汉模式的效果。

public class InnerStaticClassSingleton {

    public static InnerStaticClassSingleton getSingleton(){
        return InnerStaticClass.singleton;
    }

    private static class InnerStaticClass{
        private static final InnerStaticClassSingleton singleton = new InnerStaticClassSingleton();
    }
}

通过静态内部类只有在被调用时才被加载的特性,实现懒汉模式。

方式六:枚举方式

枚举方式是通过枚举的特性创建单例。

public enum EnumSingleton {

    SINGLETON;

    //通过枚举创建单例
    public static EnumSingleton getSingleton(){
        return EnumSingleton.SINGLETON;
    }
}

上述代码及测试代码见Github

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!