设计模式:单例模式
一、定义
单例模式:一个类只允许创建一个对象(或者叫实例),那么这个类就是一个单例类,这种设计模式就叫作单例设计模式
二、实现(例子:ID生成器)
饿汉式
- 定义:通过静态变量在类加载的时候就创建并初始化好instance
- 优点:线程安全(类加载初始化)
- 缺点:不支持延迟加载(需要使用时才初始化)
- 总结:如果初始化耗时长,推荐在程序启动的时候会就初始化好(而不是在使用时才初始化,会影响性能),同时程序启动的时候还能暴露出资源可能不够的错误(而不是在使用时才报错,会影响系统可用性)
- 代码示例:
public class IdGenerator { private AtomicLong id = new AtomicLong(0); //静态实例创建并初始化好 private static final IdGenerator instance = new IdGenerator(); //private的构造方法 private IdGenerator(){} //public的get方法 public static IdGenerator getInstance() { return instance; } public long getId() { return id.incrementAndGet(); } }
懒汉式
- 定义:在需要使用的时候才初始化
- 优点:支持延迟加载(需要使用时才初始化)
- 缺点:
getInstance()方法加了synchronized关键字,导致这个函数的病发度很低(不适合频繁调用的场景) - 总结:可以延迟加载,但如果频繁调用,会导致性能瓶颈
- 代码示例:
public class IdGenerator { private AtomicLong id = new AtomicLong(0); //静态实例只声明不初始化 private static IdGenerator instance; //private的构造方法 private IdGenerator() {} //public的get方法 public static synchronized IdGenerator getInstance() { if(instance == null) { instance = new IdGenerator(); } return instance; } public long getId() { return id.incrementAndGet(); } }
双重检测
- 定义:相比
懒汉式,instance不为空的时候将不再会加锁(而是初始化的时候才会加锁) - 优点:
instance为null的时候才会加锁(初始化) - 缺点:可能由于
指令重排序导致new完IdGenerator之后,赋值给instance之后还没来得及初始化(new了但还没执行构造函数代码,这时候其他线程拿到的instance就不是null了) - 总结:判断
instance为空才加锁,同时还可以给instance加volatile关键字(防止指令重排序) - 代码示例:
public class IdGenerator { private AtomicLong id = new AtomicLong(0); //静态实例只声明不初始化 private static IdGenerator instance; //private的构造方法 private IdGenerator() {} public static IdGenerator getInstance() { if (instance == null) { //instance不为空才加锁 synchronized (IdGenerator.class) { if (instance == null) { instance = new IdGenerator(); } } } return instance; } public long getId() { return id.incrementAndGet(); } }
静态内部类
- 定义:利用Java的静态内部类,类似饿汉式的同时,又能做到延迟加载
- 原理:初始化
IdGenerator的时候并不会创建SingletonHolder(只会等调用getInstance()方法才会加载SingletonHolder并创建instance)。保证instance的唯一性、保证创建过程的线程安全性(JVM实现) - 优点:既保证了线程安全,又能做到延迟加载
public class IdGenerator {
private AtomicLong id = new AtomicLong(0);
//private的构造方法
private IdGenerator() {}
//静态内部类
private static class SingletonHolder {
private static final IdGenerator instance = new IdGenerator();
}
public static IdGenerator getInstance() {
return SingletonHolder.instance;
}
public long getId() {
return id.incrementAndGet();
}
}
枚举
- 定义:基于枚举类型的单例实现(通过Java枚举类型本身的特性)
- 优点:简单,保证了实例创建的线程安全性和实例的唯一性
public enum IdGenertor {
INSTANCE;
private AtomicLong id = new AtomicLong(0);
public long getId(){
}
}
三、单例存在哪些问题?
单例对OOP特性的支持不友好
- 违背
抽象特性(因为直接获取了单例对象,而不是面向接口编程) - 对
继承、多态特性的支持不友好
- 违背
单例会隐藏类之间的依赖关系
- 单例类:无需显示创建、无需依赖参数传递
- 非单例类:可通过构造函数、参数传递等方式看出类之间的依赖
单例对代码的扩展性不友好
- 背景:单例类只能有一个对象实例
- 问题:如果有场景需要创建多个实例,那么对代码会有较大改动
- 场景:数据库连接池、线程池等资源池(可能要分开处理)
- 总结:单例类在某些场景下会影响代码的扩展性、灵活性
单例对代码的可测试性不友好
- 可能依赖比较重的外部资源(如DB),导致写单测的时候无法通过mock方式替换掉
- 单例类的成员变量(如IdGenerator的id变量)相当于全局变量,测试时可能会收到其他地方的修改
单例不支持有参数的构造函数
例子:比如创建一个连接池的单例对象,就无法通过参数来指定连接池的大小
解决方案:
1.通过init函数去初始化单例对象
public class Singleton { private static Singleton instance = null; //自定义参数A private final int paramA; //自定义参数B private final int paramB; //带参构造函数 private Singleton(int paramA, int paramB) { this.paramA = paramA; this.paramB = paramB; } //实例外部get方法 public static Singleton getInstance() { if(instance == null) { throw new RuntimeException("run init() first"); } return instance; } //初始化函数(带参数) public synchronized static Singleton init (int paramA, int paramB) { if(instance != null) { throw new RuntimeException("duplicate init"); } instance = new Singleton(paramA, paramB); return instance; } } //调用例子 public class Demo { public static void main(String[] args) { //先init Singleton.init(10, 20); //再使用 Singleton singleton = Singleton.getInstance(); } }2.参数放到getInstance()方法(问题:多次调用后续传的参数无效,只取初始化的参数使用)
public class Singleton { private static Singleton instance = null; //自定义参数A private final int paramA; //自定义参数B private final int paramB; //有参构造函数 private Singleton(int paramA, int paramB) { this.paramA = paramA; this.paramB = paramB; } //带参数的外部get方法 public synchronized static Singleton getInstance(int paramA, int paramB) { if(instance == null) { instance = new Sinleton(paramA, paramB); } return instance; } } public class Demo { public static void main(String[] args) { //初始化 Singleton singleton = Singleton.getInstance(10, 20); //无效,Singleton只初始化一次(10,20),所以singleton2取出还是(10,20) Singleton singleton2 = Singleton.getInstance(30, 40); } }3.(推荐使用)将参数放在配置文件(全局变量)中,在类实例化时加载
public class Config { public static final int PARAM_A = 10; public static final int PARAM_B = 20; } public class Singleton { private static Singleton instance = null; private final int paramA; private final int paramB; private Singleton(){ this.paramA = Config.PARAM_A; this.paramB = Config.PARAM_B; } public synchronized static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
四、单例有什么替代解决方案?
- 使用工厂模式
- 使用IOC容器
- …