现在在网络上搜索“java单例模式”关键词,很容易能够得到所谓的“懒汉式”,“饿汉式”两种模式。
//饿汉式
public class SingletonDemo {
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){
}
public static SingletonDemo getInstance(){
return instance;
}
}
//懒汉式
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
}
public static synchronized SingletonDemo getInstance(){
if(instance==null){
instance=new SingletonDemo();
}
return instance;
}
}
//懒汉式进阶(双重检查)
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
实际上第3种“双重检查”的单例模式不一定正确,下面是分析。
if(instance==null){//1
synchronized (SingletonDemo.class){//2
if(instance==null){//3
instance=new SingletonDemo();//4
}
}
}
上面代码块的前1,2,3,4步操作其实大家都能理解,能够答复降低synchronized带来的性能开销,但是操作4可以分解为如下的3行伪代码:
memory = allocate();//1.分配对象的内存空间
ctorInstance(memory);//2.初始化对象
instance = memory();//3.设置instance指向刚分配的内存地址
以上3步操作在一些JIT编译器上是会被重排序的,也就是说可能的执行顺序为1—>3—>2,当对象还未初始化的时候instance已经指向了对象的内存空间,此时instance!=null,这很重要!在多线程环境下,线程A中出现了指令重排,当执行到步骤2之前,线程B开始执行到if(instance==null)语句,此时由于instance!=null,返回instance对象,但是该对象还未初始化,所以线程B得到的对象依然为空!
注意:该种情况只会发生在多线程并发环境,单线程环境不会发生,因为在单线程中,只需要保证初次访问对象在初始化对象之后即可,无需在意2,3操作是否会发生重排!
解决方法:
public class SingletonDemo {
private volatile static SingletonDemo instance;//注意该行的变化
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
这个解决方案需要JDK1.5或更高版本(因为从JDK5开始使用新的JSR-133内存模型规范,这个规范增强了volatile的语义)
当声明对象的引用为volatile后,上面2,3步操作的重排序在多线程环境下将会被禁止,其执行步骤将会按如下时序执行:
以上