创建型-单例模式 Link to heading

7种单例模式的创建 Link to heading

单例模式的实现方式比较多,主要在实现上是否支持懒汉模式、是否线程安全中运用各种技巧。 当然也有一些场景不需要考虑懒加载的情况,会直接使用static静态累或属性和方法的方式进行处理,供外部调用。

0. 静态类使用 Link to heading

public class Singleton_00{
  public static Map<String, String> cache = new ConcurrentHashMap<String, String>();
}
  • 这样的静态类的方式可以在第一次运行的时候直接初始化Map类,不需要到延迟加载在使用。

1. 懒汉模式(线程不安全) Link to heading

public class Singleton_01 {
    private static Singleton_01 instance;
  
    private Singleton_01() {
    }
  
    public static Singleton_01 getInstance(){
        if (null != instance) return instance;
        instance = new Singleton_01();
        return instance;
    }
}
  • 有一个特点就是外部不允许直接创建,只能通过Singleton_01.getInstance()来初始化创建该单例。
  • 这种方式的范例确实满足了懒加载,但是如果多个访问者同时进入getInstance()中可能会出现同时存在多个实例的问题。

2. 懒汉模式(线程安全) Link to heading

public class Singleton_02 {
    private static Singleton_02 instance;
  
    private Singleton_02() {
    }
  
    public static synchronized Singleton_02 getInstance(){
        if (null != instance) return instance;
        instance = new Singleton_02();
        return instance;
    }
}
  • 这种模式虽然是安全的,但是由于加锁的原因,所有的访问者都会因为需要锁的占用导致资源浪费。

3. 饿汉模式(线程安全) Link to heading

public class Singleton_03 {
    private static Singleton_03 instance = new Singleton_03();
  
    private Singleton_03() {
    }
  
    public static Singleton_03 getInstance() {
        return instance;
    } 
}
  • 不是懒加载,无论是否用到了这个单例都会在程序启动的时候就创建。

4. 使用内部类(线程安全) Link to heading

public class Singleton_04 {
    private static class SingletonHolder {
        private static Singleton_04 instance = new Singleton_04();
    }
  
    private Singleton_04() {
    }
  
    public static Singleton_04 getInstance() {
        return SingletonHolder.instance;
    } 
}
  • 使用类的静态内部类实现的单例,既保证了线程安全又保证了懒加载,同时不会因为加锁的方式耗费性能。
  • 这主要是因为JVM虚拟机可以保证多线程并发访问的正确,也就是一个类的构造方法在多线程环境下可以被正确的加载。
  • 非常推荐

5. 双重锁校验(线程安全) Link to heading

public class Singleton_05 {
    private static Singleton_05 instance;
  
    private Singleton_05() {
    }
  
    public static Singleton_05 getInstance(){
       if(null != instance) return instance;
       synchronized (Singleton_05.class){
           if (null == instance){
               instance = new Singleton_05();
           } 
       }
       return instance;
    }
}
  • 双重锁是方法级锁的优化,减少了部分获取实例的消耗

6. CAS [AtomicReference](线程安全) Link to heading

public class Singleton_06 {
    private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<Singleton_06>();
  
    private static Singleton_06 instance;
  
    private Singleton_06() {
    }
  
    public static final Singleton_06 getInstance() {
        for (; ; ) {
            Singleton_06 instance = INSTANCE.get();
            if (null != instance) return instance;
            INSTANCE.compareAndSet(null, new Singleton_06());
            return INSTANCE.get();
        }
    }
  public static void main(String[] args) {
    System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d
    System.out.println(Singleton_06.getInstance()); // org.itstack.demo.design.Singleton_06@2b193f2d
  }
}
  • java并发库提供了很多原子类来支持并发访问的数据安全性
  • AtomicReference可以封装引用一个V实例,支持并发访问如上的单例方式就是使用了这样的一个特点。
  • 使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。
  • 当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。

7. 枚举单例 Link to heading

public enum Singleton_07 {
    INSTANCE;
    public void test(){
        System.out.println("hi~");
    }
}