单例最基本要素:
public class Singleton {
private Singleton() {
} //私有构造函数
private static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上述单例线程不安全,如在instance未实例化时,就有多个线程一起调用getInstance()方法,则会创建出多个对象。
public class Singleton {
private Singleton() {
} //私有构造函数
private static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) {
//双重检测机制
synchronized (Singleton.class){
//同步锁
if (instance == null) {
//双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}
经过两个加锁检测,看上去是很安全了,但是还是有线程安全问题,因为涉及到了JVM编译器的指令重排。
指令重排是什么意思呢?
比如java中简单的一句 instance = new Singleton,会被编译器编译成如下JVM指令:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:
memory =allocate(); //1:分配对象的内存空间
instance =memory; //3:设置instance指向刚分配的内存地址
ctorInstance(memory); //2:初始化对象
我们需要在instance对象前面增加一个修饰符volatile。
public class Singleton {
private Singleton() {
} //私有构造函数
private volatile static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) {
//双重检测机制
synchronized (Singleton.class){
//同步锁
if (instance == null) {
//双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}
volatile关键字:
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}
双重锁检查单例、静态内部类单例都可以实现线程安全的单例模式,但是不能防止用反射的方式打破单例,如下。
//获得构造器
Constructor con = Singleton.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1.equals(singleton2));
如何创建出线程安全又不能被反射破坏单例性的单例呢?
答案是用枚举实现单例模式。
/**
* 最完美的单例模式
* @author Administrator
*
*/
public class MySingleton {
public enum MyEnumSingle{
INSTANCE;
private MySingleton singleOne;
private MyEnumSingle(){
System.out.println("初始化单例");
singleOne = new MySingleton();
}
public MySingleton getInstance(){
return singleOne;
}
}
private MySingleton(){
}
public static MySingleton getInstance(){
return MyEnumSingle.INSTANCE.getInstance();
}
}