1. 什么是单例模式
单例模式一般涉及单一的一个类,该类负责创建自己的对象,同时确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类被称为单例类,它提供全局访问的方法。
其类图如下:
通过类图我们可以发现几个注意的点:
- 构造器应为 private。因为单例模式就是为了让一个类仅有一个实例,所以需要防止外部创建类实例,仅通过静态方法调用。
- 通过 public 的 getInstance() 方法获取实例,并且该方法应为 static 的。因为一个类只会有一个实例。
2. 单例模式的分类
我们根据是否懒加载将单例模式分为饿汉式和懒汉式。
2.1. 饿汉式
/**
* Created by vMars on 2019/9/24.
* 饿汉式
* 这样是线程安全的
*/
public class SingleObject_Hungry {
//创建 SingleObject_Hungry 的一个对象
private static SingleObject_Hungry instance = new SingleObject_Hungry();
//让构造函数为 private,这样该类就不能在外部实例化
private SingleObject_Hungry(){}
//获取唯一可用的对象
public static SingleObject_Hungry getInstance(){
return instance;
}
}
懒汉式不管我们是否有使用到该类的实例,只要该类被加载了,就会先创建好一个静态实例对象。
- 优点:
- 懒汉式是线程安全的,不用加锁操作,效率更高。
- 缺点:
- 因为不是懒加载,如果我们并不需要用到该类的实例可能会造成空间浪费。
2.2. 懒汉式
/**
* Created by vMars on 2019/10/10.
* 懒汉式
*/
public class SingleObject_Lazy {
private static SingleObject_Lazy instance;
private SingleObject_Lazy() {};
public static synchronized SingleObject_Lazy getInstance() {
if(instance == null) {
instance = new SingleObject_Lazy();
}
return instance;
}
}
懒汉式则是在我们调用 getInstance() 去获取类实例时判断是否已经new一个实例了,没有则new一个。
这样在多线程的情况下就可能出现两个线程同时去new实例,这是我们所不希望的,所以对 getInstance() 我们可以加锁。
但是这样的效率会很低,如当我们的静态实例已经创建了,但由于 getInstance() 加了锁,所以每次还是只能有一个线程调用。
为了降低锁的细粒度,我们可以使用双重锁校验式。
/**
* Created by vMars on 2019/9/29.
* 双重锁校验式——懒汉式
*/
public class SingleObject_Lazy {
private volatile static SingleObject_Lazy instance;
private SingleObject_Lazy() {};
//双重锁校验
public static SingleObject_Lazy getInstance() {
if (instance == null) {
synchronized (SingleObject_Lazy.class) {
if (instance == null) {
instance = new SingleObject_Lazy();
}
}
}
return instance;
}
}
双重锁校验式没有直接对 getInstance() 加锁,而是先判断 instance 是否为空,如果是则再对类加锁进行实例化。
这里我们注意到在加了锁之后还需要再判断一次 instance 是否为空,这是为了防止有多个线程通过第一个 instance 为空的判断,然后为该类重复实例多次。
这里还要注意 instance 应用 volatile 修饰,这是为了防止其指令重排,我们知道 new 的操作不是原子性的,大概可以分为三步,当我们在这三步之间去访问这个对象是会报异常的。
(不懂为啥会报异常的可以看:https://www.cnblogs.com/goodAndyxublog/p/11356402.html)