所有单例模式都有一个共性,那就是这个类没有自己的状态。也就是说无论这个类有多少个实例,都是一样的;然后除此者外更重要的是,这个类如果有两个或两个以上的实例的话程序会产生错误。
基于上述原因,非线程安全的实现方式,在此不再讨论。下面讨论的都是线程安全的一些实现方式和存在的问题。
双重加锁模式
public class Singleton{
private volatile static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){
if(null == singleton){
synchronized(Singleton.class){
if(null == singleton){
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重加锁模式
相对于普通的单例和加锁模式而言,从性能和线程安全上来说都有很大的提升和保障。然而双重加锁模式
也存在一些隐蔽不易被发现的问题。首先我们要明白在JVM创建新的对象时,主要要经过三个步骤。
-
- 分配内存
-
- 初始化构造器
-
- 将对象指向分配的内存地址
这样的顺序在双重加锁模式下是么有问题的,对象在初始化完成之后再把内存地址指向对象。但是现代的JVM会针对字节码进行调优,这样的话就有可能导致2和3的顺序是相反的,一旦出现这样的情况问题就来了。
所以更加理想的方案是利用静态内部类的方式来创建,因为静态属性由JVM确保第一次初始化时创建,因此也不用担心并发的问题出现。当初始化进行到一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。另外由于静态变量只初始化一次,所以singleton仍然是单例的。
静态内部类
的方式
public class Singleton{
private Singleton(){}
public static Singleton getInstance(){
return InnerClassSingleton.singleton;
}
private class InnerClassSingleton{
protected static Singleton singleton = new Singleton();
}
}
然而,虽然静态内部类
模式可以很好地避免并发创建出多个实例的问题,但这种方式仍然有其存在的隐患。
-
- 一旦一个实例被持久化后重新生成的实例仍然有可能是不唯一的。
-
- 由于java提供了反射机制,通过反射机制仍然有可能生成多个实例。
单例最优方案,枚举
的方式
枚举实现单例的优势
- 自由序列化;
- 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
- 线程安全;
public enum Singleton {
INSTANCE;
private Singleton(){}
}
参考文档: