单例模式

单例模式

单例模式算是最耳熟的设计模式之一了,本文就大致介绍一下单例模式相关的知识。

什么是单例模式

单例模式是指在应用程序中单例类的实例对象有且仅有一个。

单例模式有哪些优点

可以避免每次使用某个类的功能时都需要重写创建一个对象实例,但是需要注意多线程下的线程安全问题,这个功能也可以用静态方法代替。其次某些类由于特殊性在当前应用中就应该只有一个实例,例如配置信息类、一个应用的配置信息应该统一维护在一个实例中。再比如唯一递增 ID 号码生成器,如果程序中有两个对象,那就会存在生成重复 ID 的情况。

单例模式的代码实现方式

要实现一个单例模式,通常需要关注一下几点:

  • 构造函数需要是 private 访问权限,这样才能避免外部通过 new 关键字创建实例;
  • 考虑对象创建时的线程安全问题;
  • 考虑是否需要支持延迟加载;
  • 考虑 getInstance() 性能是否够高;

以下列举了常见的几种创建方式。

恶汉式

在类加载时就已经初始化好了,创建过程肯定是线程安全的,但是失去了延迟加载的特性。

public class IdGenerator {
    private final AtomicLong id = new AtomicLong(0);
    private static final IdGenerator INSTANCE = new IdGenerator();

    private IdGenerator(){
    }

    public static IdGenerator getInstance(){
        return INSTANCE;
    }

    public long getId(){
        return id.incrementAndGet();
    }

}

懒汉式(低并发度)

懒汉式支持了延迟加载,但是为了保证创建过程的线程安全在 getInstance() 方法上加了 synchronized 锁,这就导致并发读降低了。

public class IdGenerator {
    private final AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;

    private IdGenerator() {
    }

    public static synchronized IdGenerator getInstance() {
        if (instance == null) {
            instance = new IdGenerator();
        }
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }

}

懒汉式(双重检验锁)

懒汉式的另外一种实现方式,仅仅将首次创建 instance 实例的操作加上了 synchornized 锁,保证了延时加载的同事大大提升了 getInstance() 方法的并发度。

public class IdGenerator {
    private final AtomicLong id = new AtomicLong(0);
    //注意这里的实例变量需要通过「valatile」修饰,禁止指令重排序
    private static volatile IdGenerator instance;

    private IdGenerator() {
    }

    public static IdGenerator getInstance() {
        if (instance == null) {
            //注意这里加的是类级别的锁
            synchronized (IdGenerator.class){
                if (instance == null){
                    instance = new IdGenerator();
                }
            }
        }
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }

}

静态内部类实现

这种实现方式有点像饿汗式,但是却支持延迟加载。SingletonHolder 是一个静态内部类,在外部类加载的时候并不会加载内部类。只有在调用 getInstance() 静态方法时才会触发内部类 SingletonHolder 的加载,同时创建 instance 实例。instance 的唯一性,创建过程的线程安全性,都由 JVM 来保证,所以静态内部类的实现方式既支持延迟加载,又能保证创建过程的线程安全性。

public class IdGenerator {
    private final AtomicLong id = new AtomicLong(0);

    private IdGenerator() {
    }

    static class IdGeneratorHolder{
        private static final IdGenerator INSTANCE = new IdGenerator();
    }

    public static IdGenerator getInstance() {
        return IdGeneratorHolder.INSTANCE;
    }

    public long getId() {
        return id.incrementAndGet();
    }

}

枚举实现

枚举实现方式是最简单、最实用的一种实现方式。通过 java 枚举本身的特性保证了实例创建过程的线程安全性和实例的唯一性。

public enum IdGenerator {
    INSTANCE;
    private final AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }

}