设计模式-单例模式

在这里插入图片描述

单例模式主要解决的是,一个全局使用的类频繁的创建和消费。

日常场景:设置全局属性,数据库的连接池不会反复创建

主要实现有是否支持懒汉模式,是否线程安全。如果不需要考虑懒加载,可以直接使用static静态类或属性和方法来处理。

  1. 静态类使用
public class Singleton_00 {
    
    public static Map<String, String> cache = new ConcurrentHashMap<>();
    
}

这样的静态类的方式可以再第一次运行的时候直接初始化Map类,同时这里也不需要到延迟加载再使用。在不需要维持任何状态下,仅仅用于全局访问,这样的方式比较方便。如果需要被继承以及维持一些特定状态,就适合使用单例模式。

  1. 懒汉模式(线程不安全)
public class Singleton_01 {
    
    private static Singleton_01 instance;
    
    private Singleton_01() {
        
    }
    
    public static Singleton_01 getInstance() {
        if (instance != null) return instance;
        instance = new Singleton_01();
        return instance;
    }
}

单例模式有一个特点,不允许外部直接创建,不能直接new Singleton_01(),所以这里的构造方法为private。

这种方式单例确实满足了懒加载,但如果有多个访问者同时去获取对象实例,就会造成多个同样的实例并存,从而没有达到单例的要求。

  1. 懒汉模式(线程安全)
public class Singleton_02 {
    
    private static Singleton_02 instance;
    
    private Singleton_02() {}
    
    public static synchronized Singleton_02 getInstance() {
        if (instance != null) return instance;
        instance = new Singleton_02();
        return instance;
    }
}

这种方式是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占用导致资源的浪费。

  1. 饿汉模式(线程安全)
public class Singleton_03 {
    
    private static Singleton_03 instance = new Singleton_03();
    
    private Singleton_03() {}
    
    public static Singleton_03 getInstance() {
        return instance;
    }
}

这种与实例化Map一致,在程序启动的时候直接运行加载,后续有外部需要直接获取即可。但这种方式不是懒加载,就有种问题,你不需要的时候却直接实例化了,如果太大占用内存。

  1. 使用类的内部类(线程安全)
public class Singleton_04 {
    
    private Singleton_04() {}
    
    private static class SingletonHolder {
        private static Singleton_04 instance = new Singleton_04();
    }
    
    public static Singleton_04 getInstance() {
        return SingletonHolder.instance;
    }
}

使用类的静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时不会因为加锁导致消耗性能。这是因为JVM虚拟机可以保证多线程并发访问的正确性,一个类的构造方法在多线程环境下可以被正确的加载。这种方式实现单例模式非常推荐。

  1. 双重锁校验(线程安全)
public class Singleton_05 {
    
    private static volatile Singleton_05 instance;
    
    private Singleton_05() {}
    
    public static Singleton_05 getInstance() {
        if (instance != null) return instance;
        synchronized (Singleton_05.class) {
            if (instance == null) {
                instance = new Singleton_05();
            }
        }
        return instance;
    }
}

双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时,同时也满足了懒加载。

  1. CAS 线程安全)
public class Singleton_06 {

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

    private Singleton_06() {}

    public static final Singleton_06 getInstance() {
        for (;;) {
            Singleton_06 instance = INSTANCE.get();
            if (instance != null) return instance;
            INSTANCE.compareAndSet(null, new Singleton_06());
            return INSTANCE.get();
        }
    }

    public static void main(String[] args) {
        System.out.println(Singleton_06.getInstance());
        System.out.println(Singleton_06.getInstance());
    }
}

java并发库提供了很多原子类来支持并发访问的数据安全性:AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference。

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

但有个缺点就是忙等,如果一直没有获取到竟会处于死循环中。

  1. 枚举单列(线程安全)
public enum Singleton_07 {

    INSTANCE;
    public void test() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        Singleton_07.INSTANCE.test();
    }
}

这种方式:线程安全、自由串行化、单一实例。

在继承场景下是不可用的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值