设计模式学习手记之单例模式

单例模式
单例模式(singleton design pattern)是指一个类只能实例化一次,那么这个类就是单例类,这种设计模式叫做单例设计模式。
单例设计模式的用途:

  • 处理资源冲突,如对一个文件日志操作,如果可以实例化多次,那么可能存在日志写入操作相互覆盖。
  • 全局唯一类,如工具类配置文件。过滤参数的白名单。

实现单例的方式:单例可以分为

  • 恶汉单例
  • 懒汉单例

恶汉单例是指在类加载的时候,instance静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。但是,这样的实现方式不支持延迟加载(没有实际的调用前就创建好了)

public class SimpleSingleton {

   // 提前加载好,通过final修饰让其变成不可变对象。
    private final static SimpleSingleton INSTANCE = new SimpleSingleton();

    // 私有的构造器,防止外部调用构造器创建
    private SimpleSingleton() {

    }

    // 对外提供一个获取单例实例的方法
    public static SimpleSingleton getInstance() {
        return INSTANCE;
    }
}

测试恶汉模式单例:

    @Test
    public void villianSingleton() throws Exception {
        VillianSingleton instance = VillianSingleton.getInstance();
        VillianSingleton instance1 = VillianSingleton.getInstance();

        // 通过序列化测试是否只会创建一个对象。
        // 1、序列化这个这个恶汉单例
        ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream
                (new File("D:/VillianSingleton.json")));
        stream.writeObject(instance);
        // 2、在通过反序列这个对象。
        ObjectInputStream inputStream = new ObjectInputStream(
                new FileInputStream("D:/VillianSingleton.json"));
        VillianSingleton serializedObject = (VillianSingleton) inputStream.readObject();
        // 使用反射来破坏
        Class<?> aClass = Class.forName("cn.lcw.pattern.creat.singleton.VillianSingleton");
        // 获取构造器
        Constructor<?> constructor = aClass.getDeclaredConstructor();
        // 允许访问私有构造器
        constructor.setAccessible(true);
        VillianSingleton reflectVillianSingleton = (VillianSingleton)constructor.newInstance();
        Assertions.assertEquals(instance, instance1); // true
        Assertions.assertEquals(false, serializedObject == instance);
        Assertions.assertEquals(false,reflectVillianSingleton == instance);
    }

通过运行测试用例,饿汉模式的单例还是挺不住序列化与反序列、和反射的攻击,还是存在问题滴。

懒汉模式
懒汉模式:一开始并没有加载好,而是调用后才创建,相比饿汉模式,懒汉模式支持延时加载。
实现懒汉模式有三种方式:

  • 双重检查。
  • 静态内部类
  • 枚举

双重检测实现的单例

public class DoubleCheckSingleton {

    private static DoubleCheckSingleton instance;

    // 私有的构造器
    private DoubleCheckSingleton() {
    }
    
    // 对外提供获取单例的方法
    public static DoubleCheckSingleton getInstance() {
        if (instance == null) {
            // 加上一个傻逼的类级别锁
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

使用静态内部类实现的单例
使用内部静态类,和饿汉模式有点类似但是,又它又支持延时加载。
实现如下:


public class InnerClassSingleton implements Serializable {

    // 静态内部类
    private static class Singleton {
        private static final InnerClassSingleton instance = new InnerClassSingleton();
    }

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

测试内部类是否安全

@Test
    public void innerClassSingletonTest() throws Exception{

        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        InnerClassSingleton instance2 = InnerClassSingleton.getInstance();

        // 通过序列化测试是否只会创建一个对象。
        // 1、序列化这个这个恶汉单例
        ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream
                (new File("D:/InnerClassSingleton.json")));
        stream.writeObject(instance);
        // 2、在通过反序列这个对象。
        ObjectInputStream inputStream = new ObjectInputStream(
                new FileInputStream("D:/InnerClassSingleton.json"));
        InnerClassSingleton serializedObject = (InnerClassSingleton) inputStream.readObject();
        // 使用反射来破坏
        Class<?> aClass = Class.forName("cn.lcw.pattern.creat.singleton.InnerClassSingleton");
        // 获取构造器
        Constructor<?> constructor = aClass.getDeclaredConstructor();
        // 允许访问私有构造器
        constructor.setAccessible(true);
        InnerClassSingleton reflectVillianSingleton = (InnerClassSingleton)constructor.newInstance();
        Assertions.assertEquals(instance, instance2); // true
        Assertions.assertEquals(true, serializedObject == instance);
        Assertions.assertEquals(false,reflectVillianSingleton == instance);
    }

通过运行测试用例,使用静态内部类也是不能保证全局只生成一个对象。

使用枚举实现懒汉模式



public enum IdGenerator {
  INSTANCE;
  private AtomicLong id = new AtomicLong(0);
 
  public long getId() { 
    return id.incrementAndGet();
  }
}


直接使用饿汉模式简单粗暴,如果没有明确要求使用延时加载的。
总结:

  • 单例对 面向对象 特性的支持不友好,择将某个类设计成到单例类,也就意味着放弃了继承和多态这两个强有力的面向对象特性,也就相当于损失了可以应对未来需求变化的扩展性.
  • 单例会隐藏类之间的依赖关系,单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽
  • 单例对代码的扩展性不友好,
  • 单例对代码的可测试性不友好.
  • 单例对代码的可测试性不友好
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值