设计模式之单例模式

单例模式

概述

在程序运行的时候,通常会产生很多实例,例如String类和字符串是一对一的关系,当创建了1000个字符串时,就会有1000个实例生成。但是在实际开发过程中,我们需要在程序中,某个东西只会存在一个,就会有只能创建一个实例的需求。例如在程序中开辟缓存时,缓存对象我们只希望他创建一次。

原则

  • 私有构造(组织类被通过常规方法实例化)
  • 以静态方法或枚举返回实例(保证实例的唯一性)
  • 确保实例只有一个,尤其是多线程环境(保证实例在创建时的线程安全)
  • 确保反序列化时不会重新构建对象

静态类使用

这种方法在程序内部做缓存时非常的常见,ConcurrentHashMap 的优势在于兼顾性能和线程安全,一个线程在进行写操作时,它会锁住一小部分,其他部分的读写不受影响,其他线程访问没上锁的地方不会被阻塞

public class Singleton00 {
    private static Map<String,Object> cache = new ConcurrentHashMap<>();
}

饿汉模式

饿汉模式指的是在程序启动时直接创建对象,外部调用时只能通过getInstance方法来获取实例,这种情况不太适用于初始化对象很复杂的情况,会导致程序启动速度下降。构造方法使用private为了强制要求使用getInstance方法获取实例

public class Singleton01 {
    private static Singleton01 singleton01 = new Singleton01();

    private Singleton01() {
    }

    public static Singleton01 getInstance() {
        return singleton01;
    }
}

//测试
@SpringBootTest
class Practice800ApplicationTests {
    @Test
    void contextLoads() {
        Singleton01 instance1 = Singleton01.getInstance();
        Singleton01 instance2 = Singleton01.getInstance();
        if (instance1 == instance2){
            System.out.println("两者相同");
        }
    }
}

懒汉模式(线程不安全)

懒汉模式指的是在对象在第一次使用时才被创建,但这种创建模式是线程不安全的,如果有多个线程一起访问getInstance方法,可能会创建多个Singleton02对象

public class Singleton02 {
    private static Singleton02 singleton02;

    private Singleton02() {
    }

    public static Singleton02 getInstance() {
        if (singleton02 != null){
            return singleton02;
        }
        singleton02 = new Singleton02();
        return singleton02;
    }
}

懒汉模式(线程安全)

使用synchronized修饰getInstance方法,可以保证该实例的创建是线程安全的

public class Singleton03 {
    private static Singleton03 singleton03;

    private Singleton03() {
    }

    public static synchronized Singleton03 getInstance() {
        if (singleton03 != null){
            return singleton03;
        }
        singleton03 = new Singleton03();
        return singleton03;
    }
}

但随之而来的问题是,这样对于创建完对象后,调用getInstance方法也会需要排队,效率会随之下降,我们可以使用Volatile关键字+synchronized代码块来解决

被Volatile关键字修饰的对象,具有可见性,也就是说当一个线程修改该对象,另外的线程可以感知到对象的变化

public class Singleton04 {
    private static volatile Singleton04 singleton04;

    private Singleton04() {
    }

    public static Singleton04 getInstance(){
        if (singleton04 != null){
            return singleton04;
        }
        synchronized (Singleton04.class){
            if (null == singleton04){
                singleton04 = new Singleton04();
            }
        }
        return singleton04;
    }
}

使用类的内部类

使用类的静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时不会因为加锁的方式耗费性能。

public class Singleton05 {
    private static class SingletonHolder{
        private static Singleton05 singleton05 = new Singleton05();
    }

    private Singleton05() {
    }

    public static Singleton05 getInstance(){
        return SingletonHolder.singleton05;
    }
}

CAS「AtomicReference」(线程安全)

使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。

public class Singleton06 {
    private static final AtomicReference<Singleton06> INSTANCE = new AtomicReference<>();

    private Singleton06() {
    }

    public static Singleton06 getInstance(){
        for ( ; ; ){
            //get() 获取AtomicReference的当前对象引用值。
            Singleton06 singleton06 = INSTANCE.get();
            if (null != singleton06) {
                return singleton06;
            }
            //compareAndSet(expect,update),更新引用值
            //expect指的是当前的对象的值,update则是需要设置的新引用值。
            INSTANCE.compareAndSet(null,new Singleton06());
            return INSTANCE.get();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值