创建型-单例模式

单例模式虽然简单但很重要,在面试中也是高频考点,需要重点理解,灵活运用,特别是每种实现方式的优缺点以及演进之路。

概述

单例模式:私有化构造器,确保一个类只有一个实例,并提供该实例的全局访问点。对于一些需要频繁创建销毁的对象,使用单例模式可以节省系统资源,提供系统性能。属于创建型设计模式的一种。

案例场景

实现方式

饿汉式(线程安全)

public class Singleton_00 {

    private Singleton_00(){};
	// 在类加载时就实例化完成
    public static Singleton_00 singleton_00 = new Singleton_00();

}
  • JVM启动时会加载所有类,这种方式在类加载时就进行了实例化,后续对外提供访问权限,因此是线程安全的。
  • 相对于延迟加载的方式,我们称这种单例实现方式为饿汉式。优点是实现简单,线程安全。缺点就是类加载时实例化会先消耗内存空间,浪费系统资源。
  • 如果确保这个实例在运行中肯定要被访问,那选择这种方式也是没有问题的。

懒汉式(线程不安全)

    private static Singleton_02 singleton_02;

    private Singleton_02() {}
    
    public static Singleton_02 getBean() {
        if (null == singleton_02) {
            return new Singleton_02();
        }
        return singleton_02;
    }
  • 延迟加载的实现方式,调用getBean方法才会去实例化类
  • 线程不安全,如果多个线程同时进入getBean方法,第一个线程还未实例化new Singleton_02()完成,第二个线程就通过了if (null == singleton_02)判断,就可能会造成类多次实例化。

懒汉式(线程安全)

那么,为了解决上述这种方式带来线程不安全的问题,我们可以通过加锁的思路来尝试完善。

    private static Singleton_03 singleton_03;

    private Singleton_03() {}
    
    public static synchronized Singleton_03 getBean() {
        if (null != singleton_03) {
            return singleton_03;
        }
        singleton_03 = new Singleton_03();
        return singleton_03;
    }
  • 使用synchronized 关键字对方法进行加锁,让方法同一时间只能有一个线程进入,也能够避免多次实例化的问题
  • 但是,把锁加到方法上,所有的访问都需要等待锁,也导致了资源的浪费

双重校验锁(推荐)

那怎么样才能避免资源浪费,又能保证线程安全,避免多次实例化呢?

    private static Singleton_05 singleton_05;
    
    private Singleton_05() {}
    
    public static Singleton_05 getBean() {
        if (null != singleton_05) {
            return singleton_05;
        }
        synchronized (Singleton_05.class) {
            if (null == singleton_05) {
                singleton_05 = new Singleton_05();
            }
            return singleton_05;
        }
    }
  • 双重校验锁的方式是对上述方式的优化,减少了获取实例的耗时
  • 双重校验锁支持懒加载,并且保证了线程安全,还有效提升了性能,简直完美,推荐使用。

静态内部类(推荐)

还有静态内部类也是非常推荐的一种单例实现方式,重点哦

    private Singleton_06(){}

    private static class innerClass {
        private static Singleton_06 instance = new Singleton_06();
    }

    public static Singleton_06 getInstance() {
        return innerClass.instance;
    }
  • 使用静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时也不会因为加锁而耗费性能

枚举实现(推荐)

public enum Singleton_07 {
    INSTENCE
    ;
    void getBean() {
        System.out.println("枚举方式实现单例模式");
    }
}
    // 调用方式
    public void test() {
        Singleton_07.INSTENCE.getBean();
    }
  • Effective Java 作者推荐使⽤枚举的⽅式解决单例模式,此种⽅式平时比较少⽤到,但它是公认的实现单例的最佳方法

补充:CAS(AtomicReference)

Java并发库提供了很多原子类来支持并发访问的数据安全性,AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference
AtomicReference可以封装引用一个实例,支持并发访问

 private static final AtomicReference<Singleton_04> INSTANCE = new AtomicReference<Singleton_04>();

    private static Singleton_04 instance;

    private Singleton_04(){}

    public static final Singleton_04 getInstance() {
        while (true) {
            Singleton_04 singleton_04 = INSTANCE.get();
            if (null != instance) {
                return instance;
            }
            INSTANCE.compareAndSet(null, new Singleton_04());
            return INSTANCE.get();
        }
    }
  • 使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层的硬件实现,来保证线程安全。
  • 相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性
  • 缺点就是忙等,如果一直没有获取到将会处于死循环中

适用场景

工具类对象、以及频繁访问数据库或文件的对象(比如数据源,session工厂)

总结

总的来说,在平时开发中,一般比较推荐的单例实现方式就是饿汉式、双重校验锁、静态内部类、枚举实现、CAS(AtomicReference),具体使用哪种方式还要根据业务场景具体情况具体分析,前提是需要充分了解各种实现方式的优缺点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值