懒汉式–第一次调用时加载
1. 第一版 上来没有想很多,直接敲起,使用synchronized保证线程安全,代码如下:public class Single{ private static Single instance; //将构造函数声明称私有的 private Single(){}; //使用synchronized保证安全 public static synchronized Single getSingle(){ if(instance==null){ instance=new Single(); } return instance; } }
2.第一版的缺点:当两个线程 同时访问线程锁的时候,出现竞争,一个获得锁,一个等待,等第一个线程执行完之后,第二个线程才能获得锁,执行判断条件.这样效率会很低.
看了一篇博客后发现,可以使用双重判断进行优化,改进版如下:public class Single2 { private static Single2 instance; // 将构造函数声明称私有的 private Single2() { }; private Single2 getSingle() { //第一重循环判断instance存在时跳过循环,直接返回 if (instance == null) { //使用synchronized代码块保证,多线程同时访问时线程同步 synchronized (Single.class) { //防止等待的线程进入创建实例 if(instance==null){ instance=new Single2(); } } } return instance; } }
3.博客中提到的完美版本:volatile关键字版
解决上面版本中可能出现的指令重排导致不稳定结果的出现:1.给instance分配内存 2.调用构造函数初始化成员变量,创建实例 3.将instance指向分配的内存空间//注意是指向内存空间,不是实例
在JVM中可能会出现指令重排问题而导致步骤2和3的顺序不能保证,当线程1如果在执行完指令1后直接执行指向3,如果在其执行完3而未执行2时,线程2开始访问第一层if判断,注意这时的instance就已经不是null了,它已经指向了jvm分配的内存空间,这样线程2就会直接返回中间态instance,继续执行下去将会报错.
解决方法使用volatile关键字修饰instance,volatile会产生内存屏障,从而禁止指令重排
public class Single3 {
//使用volatile關鍵字,禁止指令重排
private static volatile Single3 instance;
// 将构造函数声明成私有的
private Single3() {
};
private Single3 getSingle() {
//第一重循环判断instance存在时跳过循环,直接返回
if (instance == null) {
//使用synchronized代码块保证,多线程同时访问时线程同步
synchronized (Single3.class) {
//防止等待的线程进入创建实例
if(instance==null){
instance=new Single3();
}
}
}
return instance;
}
}
2.饿汉式 –在类装载时创建实例
这种情况下实例在类加载器加载类初始化,这时jvm会保证线程同步
public class Single4{
private static final Single4 INSTANCE =new Single4();
private Single4(){};
public static Single4 geSingle(){
return INSTANCE;
}
}