单例模式就是一个类只有一个实例。
单例模式的两个特点:
- 构造方法私有,外部不能直接new出对象。
- 提供一个 public 的静态方法,使得外部通过该方法获取单例类的实例。
单例模式根据实例的创建时机,大致又分为两种:饿汉式和懒汉式。
饿汉式单例是指单例类加载的时候就初始化一个对象,不管之后的程序会不会用到。
public class SingletonApp {
private static SingletonApp instance = new SingletonApp();
private SingletonApp() {}
public static SingletonApp getInstance() {
return instance;
}
}
饿汉式还有一个变种,就是把实例化放到静态代码块中。
public class SingletonApp {
private static SingletonApp instance = null;
static {
instance = new SingletonApp();
}
private SingletonApp() {}
public static SingletonApp getInstance() {
return instance;
}
}
懒汉式单例类似懒加载,只有程序第一次用到的时候,才开始实例化对象。
public class SingletonApp {
private static SingletonApp instance = null;
private SingletonApp() {}
public static SingletonApp getInstance() {
if (instance == null) {
instance = new SingletonApp();
}
return instance;
}
}
以上代码在单线程环境下没有什么问题,但是在多线程环境下,是线程不安全的。可用下面的代码做测试:
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonApp.getInstance();
}, "SingletonApp" + i).start();
}
}
运行后发现,上面两种单例创建方式都会出现重复创建实例的情况,可见线程都不安全。那怎么实现线程安全的单例模式呢?方法就是:双重校验 + volatile
public class SingletonApp {
private volatile static SingletonApp instance = null;
private SingletonApp() {}
//DCL:double checked lock
public static SingletonApp getInstance() {
if (instance == null) {
//可在getInstance方法上加入synchronized修饰,即可解决多次创建单例的bug,但不建议用,效率太低。
synchronized (SingletonApp.class){
if (instance == null) {
instance = new SingletonApp();
}
}
}
return instance;
}
}
注意点:
- synchronized 是为了加锁。也可在getInstance方法上加入synchronized修饰,但不建议用,效率太低。
- 两次判断instance == null,这就是双重校验,防止线程A创建完实例后释放锁,线程B获取锁进入同步代码块,如果没有二次判断将会重复创建实例。所以,必须再加一层判断。
- volatile是为了保证有序性,因为 instance = new SingletonApp() 操作不是原子性的,所以必须保证底层创建实例的操作按顺序执行。