单例模式
单例模式(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();
}
}
直接使用饿汉模式简单粗暴,如果没有明确要求使用延时加载的。
总结:
- 单例对 面向对象 特性的支持不友好,择将某个类设计成到单例类,也就意味着放弃了继承和多态这两个强有力的面向对象特性,也就相当于损失了可以应对未来需求变化的扩展性.
- 单例会隐藏类之间的依赖关系,单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽
- 单例对代码的扩展性不友好,
- 单例对代码的可测试性不友好.
- 单例对代码的可测试性不友好