创建型模式——单例模式
(一)概述
单例模式是最简单的设计模式之一,属于创建型模式,它提供了一种特殊的创建对象的方式,确保全局中只有单个对象被创建。这个设计模式主要目的是想在整个系统中只能出现类的一个实例,即一个类只有一个对象。
单例模式可以非常有效地节约资源,主要有以下两点:
- 由于频繁使用已经创建完成的对象,可以省略多次创建对象所花费的时间,这对于那些重量级的对象而言,效果非常明显。
- 因为不需要频繁创建对象,GC压力也减轻了,而在GC中会有STW(stop the world),从这一方面也节约了GC的时间。
但是,单例模式很多时候不得不考虑多线程并发的问题。虽然单例模式本身非常简单,但是加上并发问题之后,使得它变为一种非常复杂且容易出错的设计模式。下面我们来看看单例模式的实现方式:
(二)饿汉模式实现
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
饿汉模式是单例模式最简单也是最基础的实现方式之一。这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。 这种方式基于类加载机制避免了多线程的同步问题,因此非常通俗易懂。
(三)非线程安全的懒汉模式实现
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这是最基础的懒加载模式的单例模式。我们可以发现与饿汉模式相比,懒汉模式将对象创建时机放到了使用时,当第一次使用这个对象时,才会对对象进行初始化。因此,这种方式能更有效的利用资源。但是,重点来了,这段代码无法在多线程的环境下正常运行(关键在于创建对象时可能出现的指令重排序,这里就不展开了)。
(四)线程安全的懒汉模式实现
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
我们可以看到这个懒汉模式和上面的只有一个区别,就是多了一个synchronized关键字,这个关键字使得在同一时间只有一个线程能进入getInstance()的代码块中,避免了多线程情况下的问题。但是,synchronized造成了很多不必要的同步开销,使得这段代码的效率极为低下。因为可能出现问题的时候只是创建对象时,显然我们的对象只会创建一次,而synchronized使得每次使用对象时都要进行同步,造成极大的资源浪费。因此,这种实现方式基本可以弃用。
我们可以思考一下为什么这段代码效率低,就能找到改进方法。按照道理,我们只需要在第一次创建对象时进行同步,但是这段代码实际将后面每次使用对象的过程也进行了同步。因此,我们可以认为他的封锁粒度过大,因此我们要着手降低封锁的粒度。
(五)双重检查锁的懒汉模式实现
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
synchronized同步块里面能够保证只创建一个对象。但是通过在synchronized的外面增加一层判断,就可以在对象一经创建以后,不再进入synchronized同步块,这种方案不仅减小了锁的粒度,保证了线程安全,同时性能方面也得到了大幅提升。同时这里要注意volatile不能少,这个很关键,volatile一般用于多线程的可见性,但是这里是用来防止指令重排序的。
(六)静态内部类懒汉模式实现
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
这是双重检查锁的懒汉模式的替代,一种比较巧妙的实现方式。第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机才会加载SingletonHolder并初始化sInstance对象,这样不仅能确保线程安全也能保证唯一性,所以推荐使用静态内部类单例模式。
(七)基于枚举类的饿汉模式实现
public enum Singleton{
INSTANCE;
}
默认枚举实例的创建是线程安全的,所以不需要担心线程安全的问题。通过枚举类,能自动避免序列化/反序列化攻击,以及反射攻击(枚举类不能通过反射生成)。
2020年7月9日