先看一下一般的实现方式
public class lazySingleton {
private static lazySingleton m_instance=null;
private lazySingleton() {
// TODO Auto-generated constructor stub
System.out.println("构造函数");
}
public static lazySingleton getInstance(){
if(m_instance==null){//b
synchronized (lazySingleton.class) {
if(m_instance==null){
m_instance=new lazySingleton(); //a
}
}
}
return m_instance;//c
}
public void print(){
System.out.println("print");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
lazySingleton.getInstance().print();
}
}
这种实现方式,在单线程模式下完全没有问题。但在多线程模式下,则可能引发报错。 原因如下:
- m_instance=new lazySingleton();语句编译之后其实是包含两条指令,new lazySingleton()指令和给m_instance赋值。由于编译时或CPU执行时指令重排序,可能存在先给m_instance赋值,后初始化对象的情况。这时,就出现一种情况,m_instance值不为null,但是m_instance对象还没有实例化完成。
- 当线程A执行到a处时,m_instance值不为null,但是m_instance对象还没有实例化完成。线程B由于m_instance值不为null,从b出到c处返回m_instance对象。
- 当线程B调用m_instance的方法时,将引发对象尚未初始化错误。
解决:针对引发问题的主要原因1和2,分别提出解决的办法。
-
方法一:private static volatile lazySingleton m_instance=null;在m_instance前添加volatile 关键字,提醒编译器和运行时环境,在volatile变量上的操作不能与其它操作重排序,从而避免后续问题产生。
-
方法二:在getInstance方法前添加synchronized 同步锁,保证同时只会有一个线程getInstance方法,也就保证了在A线程返回m_instance之前,其他线程都是阻塞状态。也就不会出现m_instance值不为null,但是m_instance对象还没有实例化完成的情况。
除了以上两种方法,还有一种简单安全还可靠的方法,那就是依赖JVM的静态类的静态属性实现单例模式。如下
public class Singleton {
static class SingletonHolder {
static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
此种写法利用了类加载器的加载原理,每个类只会被加载一次,这样单例对象在其内部静态类被加载的时候生成,而且此过程是线程安全的。