1.饿汉式(静态常量) 推荐指数:★★☆☆☆
优点:不会有线程安全问题。
缺点:在类加载的时候就创建对象,如果一直没使用到该对象的话,就造成了内存浪费,如果对象初始化的工作很多也会影响性能。
public class Singleton1 {
private final static Singleton1 INSTANCE = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance() {
return INSTANCE;
}
}
2.饿汉式(静态代码块) 推荐指数:★★☆☆☆
优缺点和第一种方式基本一致。
public class Singleton2 {
private final static Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2() {
}
public static Singleton2 getInstance() {
return INSTANCE;
}
}
3.懒汉式(线程不安全) 推荐指数:★☆☆☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:有线程安全问题。
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
public static Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
4.懒汉式改造(线程安全) 推荐指数:★★☆☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:没有线程安全问题,但是有很大的性能问题,当多个线程同时到达getInstance()方法时,需要排队进入。
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {
}
public synchronized static Singleton4 getInstance() {
if (instance == null) {
instance = new Singleton4();
}
return instance;
}
}
5.懒汉式改造(线程不安全) 推荐指数:☆☆☆☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:线程不安全,还有性能问题!多个线程在synchronized那一行排队,进入代码块后一样会创建多个对象。
public class Singleton5 {
private static Singleton5 instance;
private Singleton5() {
}
public static Singleton5 getInstance() {
if (instance == null) {
synchronized (Singleton5.class) {
instance = new Singleton5();
}
}
return instance;
}
}
6.双重检查缺陷版(线程不安全) 推荐指数:☆☆☆☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:线程不安全,这里的线程不安全要涉及到对象的创建过程和指令重排序。
public class Singleton6 {
private static Singleton6 instance;
private Singleton6() {
}
public static Singleton6 getInstance() {
if (instance == null) {
synchronized (Singleton6.class) {
if (instance == null) {
instance = new Singleton6();
}
}
}
return instance;
}
}
详细讨论:
这个优化我们利用了双重检测机制和同步锁,这种方式也称为双重同步锁单例模式
,但是这个案例还是线程不安全的,大家通过代码层面的分析后,发现确实不会有线程安全问题,那问题出现在哪呢?这个其实要和对象创建步骤和Jvm指令重排挂钩,我们正常创建对象的指令步骤是这样的:
- memory = allocate() 分配对象的内存空间
- ctorInstance() 初始化对象
- instance = memory 设置instance指向刚分配的内存
但是因为JVM和cpu优化,发生了指令重排,
- memory = allocate() 分配对象的内存空间
- instance = memory 设置instance指向刚分配的内存
- ctorInstance() 初始化对象
我们可以结合代码,假如A线程进入同步代码块执行instance = new Singleton6(),执行到“instance = memory 设置instance指向刚分配的内存”,这个时候B线程在第一次执行“if (instance == null)”,发现instance不为空,直接返回instance实例,其实线程B得到的这个实例并没有完全初始化
(A还没有执行完对象的初始化步骤)就已经使用了。
关于对象创建过程可以参考 Java对象的创建、内存布局和访问定位
那如何禁止指令重排呢,很简单,用我们前面文章提到的volatile
关键字就可以了
7.双重检查优化版(线程安全) 推荐指数:★★★★☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题。
缺点:复杂。
public class Singleton7 {
private volatile static Singleton6 instance;
private Singleton6() {
}
public static Singleton6 getInstance() {
if (instance == null) {
synchronized (Singleton6.class) {
if (instance == null) {
instance = new Singleton6();
}
}
}
return instance;
}
}
8.静态内部类方式 推荐指数:★★★☆☆
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题,线程安全。
缺点:没有太大缺点。
public class Singleton8 {
private Singleton7() {
}
private static class SingletonInstance {
private static final Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance() {
return SingletonInstance.INSTANCE;
}
}
9.枚举单例 推荐指数:★★★★★
优点:使用到的时候才会创建对象,不会造成各种资源浪费问题,线程安全。
缺点:最优方案。
public class Singleton9 {
private Singleton9() {
}
private enum SingletonEnum {
/**
* 枚举单例
*/
SINGLETON;
private Singleton9 singleton;
SingletonEnum() {
singleton = new Singleton9();
}
public Singleton9 getInstance() {
return singleton;
}
}
public Singleton9 getInstance() {
return SingletonEnum.SINGLETON.getInstance();
}
}