前言
单例模式是最常用、也最简单的设计模式,但是要写出高性能并且安全单例模式确很困难。面试的时候,也常问你能写出几种单例模式,并说出其优缺点。今天就一起来盘点一下单例模式所有写法。
1. 饿汉模式
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
优点是线程安全的,只有一个实例。缺点是即使没有调用getInstance()方法,instance实例在类加载时被初始化了。
2. 懒汉模式,非线程安全
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
虽然代码判断了当instance不等于空时,才进行初始化。但是当多个线程同时执行到if(instance==null)的时候,一个线程执行完new Singleton(),其他线程也会接着执行,导致new出多个实例。
3. 懒汉模式,线程安全
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在getIntance()方法上增加了synchronized锁,可以实现只有一个线程进行实例化对象,但是instance已经被实例化过,还是只能有一个线程访问getInstance()方法,性能急剧下降。
4. 双重检查锁模式,非线程安全
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
虽然经过了两次判空,保证每次只有一个线程执行new Singleton(),但还是有问题。因为instance = new Singleton()这条语句并不是原子的,而是包含了三个步骤:
1. 栈上分配内存空间
2. 堆上初始化对象
3. 栈上引用指向堆内存空间地址
jvm为了提升执行效率,会对这三个步骤重排序。单线程不会有问题,多线程的时候,其中一个线程的步骤2和3交换后,其他线程会判断intance不会空,当使用instance对象的时候又会报空指针异常。
5. 双重检查锁模式,线程安全
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
与上一个不同的是instance对象用了volatile修饰,会禁止jvm对该对象的指令重排序,从而避免的上述问题。
6. 静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
这种方式是线程安全的,也不会有性能问题,也是《Effective Java》推荐的。
7. 枚举类
public enum Singleton {
INSTANCE;
}
最简单的一种,也是最推荐的一种。可以直接使用
Singleton instance = Singleton.INSTANCE;
是线程安全的,也不会有性能问题。