定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的优点:
只有一个实例,减少了内存开支;
减少了系统性能开销;
可避免对资源的过多占用;
可在系统设置全局的访问点,优化和共享资源访问。
单例模式的缺点:
没有接口,扩展困难;
对测试是不利的;
与单一职责原则有冲突,单例模式把要单例和业务逻辑融合在一个类中。
单例模式的使用场景:
要求生成唯一序列号的环境;
整个项目中需要一个共享访问点或共享数据;
创建一个对象需要消耗的资源过多;
需要定义大量的静态常量和静态方法;
单例模式的注意事项:
1、高并发模式下,需要注意线程同步问题;
// 饿汉模式
public class Singleton{
private static final Singleton singleton = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return singleton;
}
}
2、饿汉模式在类加载的时候就实例化,不会有线程安全问题,但是可能会多出很多不用的实例。
// 「懒汉」线程不安全的单例
public class Singleton{
private static Singleton singleton = null;
// 限制产生多个对象
private Singleton(){
}
// 获取单例实例
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
3、以上单例在低并发下尚可,并发量高了之后,若线程A执行到new Singleton(),还未完成赋值时,
线程B执行到了singleton==null的判断,那么此刻题条件为真,线程A,B将各获得一个实例。
// 懒汉模式
public class Singleton{
private static Singleton singleton = null;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
每次调用方法都需获取锁释放锁,对性能损耗大。
// 懒汉双重检测锁式
public class Singleton{
private static volatile Singleton singleton = null;
private Singleton(){
}
public static Singleton getInstance(){
if(singleton == null){
synchronize(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
详细解释:
双重检测锁是为了兼顾「线程安全」和「性能」:既保证只创建一个实例,又避免每次调用 getInstance() 都加锁,造成性能浪费。
1. 第一次检查:if (singleton == null)
不加锁,快速判断实例是否已经创建。
如果已经创建,直接返回,避免加锁的开销。
如果未创建,才进入同步块。
2. 加锁:synchronized (Singleton.class)
防止多个线程同时进入创建逻辑,确保线程安全。
只有一个线程能进入同步块,其他线程被阻塞。
3. 第二次检查:if (singleton == null)
防止重复创建实例。
因为:
第一个线程进入同步块后,创建了实例。
第二个线程刚被唤醒,如果不再次检查,会以为实例还没创建,又会 new 一次,破坏单例。
⚠️ 注意:必须加 volatile
你代码中这句是关键:
private static volatile Singleton singleton = null;
volatile 防止指令重排序,确保其他线程看到的是完全初始化好的实例。
否则可能出现:一个线程正在构造对象,另一个线程看到的是一个半初始化的对象引用,导致程序出错。
✅ 总结一句话(再强调):
双重检查锁是为了:第一次快速判断避免加锁,第二次加锁后判断避免重复创建,兼顾性能与线程安全。
静态内部类式
// 静态内部类式
public class Singleton{
private static class SingletonHolder{
private static Singleton INSTANCE = new Singleton();
}
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
1、静态内部类在加载的时候不会被加载,在使用的时候才会被加载,但是在加载的时候又是线程安全的;既能延迟加载解决性能问题,又是线程安全的。
2、私有构造器可以被反射调用,破坏单例性。
修改构造器,第多次调用构造器抛出异常;
3、可以通过readObject方法序列化反序列化出新的实例,破坏了单例性。
覆写readResolve方法,防止以上情况下产生新的实例;
使用枚举的方式实现实例,枚举底层禁用了readObject方法,且只有私有构造器;