单例模式

定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式的优点:

只有一个实例,减少了内存开支;
减少了系统性能开销;
可避免对资源的过多占用;
可在系统设置全局的访问点,优化和共享资源访问。

单例模式的缺点:

没有接口,扩展困难;
对测试是不利的;
与单一职责原则有冲突,单例模式把要单例和业务逻辑融合在一个类中。

单例模式的使用场景:

要求生成唯一序列号的环境;
整个项目中需要一个共享访问点或共享数据;
创建一个对象需要消耗的资源过多;
需要定义大量的静态常量和静态方法;

单例模式的注意事项:

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方法,且只有私有构造器;

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容