代码设计

[设计模式] 单例模式

2019-08-18  本文已影响0人  离兮丶斜阳

[设计模式] 单例模式

@TOC

手机用户请横屏获取最佳阅读体验,REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。

平台 地址
CSDN https://blog.csdn.net/sinat_28690417
简书 https://www.jianshu.com/u/3032cc862300
个人博客 https://yiyuery.github.io/NoteBooks/

单例模式:确保一个类只有一个实例,并提供一个全局访问点

经典单例模式

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

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

线程安全问题优化

synchronized 关键词

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

使用静态成员变量创建单例

public class StaticSingleton {

    private static StaticSingleton instance = new StaticSingleton();

    private StaticSingleton() {
    }

    public static StaticSingleton getInstance() {
        return instance;
    }
}

双重检查加锁

public class DoubleLockSingleton {

    private volatile static DoubleLockSingleton instance;

    private DoubleLockSingleton() {
    }

    public static synchronized DoubleLockSingleton getInstance() {
        //检查单例是否已存在
        if (instance == null) {
            //多线程进入获取初始化实例时
            synchronized (DoubleLockSingleton.class) {
                //进入区块后,再检查一次,如果还为null,才创建实例
                if (instance == null) {
                    instance = new DoubleLockSingleton();
                }
            }
        }
        return instance;
    }
}

登记式/静态内部类

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟使用静态成员变量创建单例方式不同的是:后者只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。

因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比使用静态成员变量创建单例方式就显得很合理(这也是延迟初始化的优势所在)。

public class StaticHolderSingleton {

    private static class SingletonHolder {
        private static final StaticHolderSingleton INSTANCE = new StaticHolderSingleton();
    }

    private StaticHolderSingleton (){}

    public static StaticHolderSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

枚举方式

这种方式可以组合前文的单例创建方式来提供一个单例的管理入口。

@Getter
public enum SingletonEnum {

    /**
     * 单例
     */
    INSTANCE(Singleton.getInstance());

    private Singleton singleton;

    SingletonEnum(Singleton singleton) {
        this.singleton = singleton;
    }
}

或是这样

@Getter
public enum SingletonEnum {

    /**
     * 单例
     */
    INSTANCE;

    public void method(){
        //TODO STH
    }
}

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

而且不能通过 reflection attack 来调用私有构造方法。

总结

上述实现方式总结如下:

方式 昵称 线程安全 延迟初始化 特点
经典实现方式 懒汉式,线程不安全 不支持多线程
synchronized 关键词 懒汉式,线程安全 多线程性能不佳
使用静态成员变量创建单例 饿汉式 对于项目规模大,单例多的时候谨慎使用
双重检查加锁 懒汉式 多线程性能良好
登记式/静态内部类 \ 多线程性能良好,但是只支持静态域
枚举方式 \ \ \ 简洁

单例模式特点

设计原则

对于公共使用的,全局唯一的访问点,可以使用单例封装内部实现逻辑后对外提供能力,而非多处创建依赖对象,消耗系统资源。

REFERENCES

更多

扫码关注架构探险之道,回复文章标题,获取本文相关源码和资源链接

在这里插入图片描述

知识星球(扫码加入获取历史源码和文章资源链接)

在这里插入图片描述
上一篇 下一篇

猜你喜欢

热点阅读