说明

// 1.双重检验,单例模式,懒汉式,保证线程安全

实现

// #.final修饰,避免子类继承,覆盖父类方法,破坏单例
public final class Singleton implements Serializable{
	// #.私有构造方法,避免被外面使用,但无法避免反射构造实例
    private Singleton(){}
	// #.volatile修饰避免指令重排序,读写屏障
    private static volatile Singleton instance;
    
    public static Singleton getInstance(){
    	//#.第一个if判断是否为空,不为空直接返回,避免synchronized同步代码块的执行,多线程场景下频繁加锁会影响性能
        if(instance == null){
        	// 首次访问会同步,之后的使用没有synchronized
            synchronized (Singleton.class){
            	// #.第二个if判断是否为空,当a线程优先获得锁,执行到此处,b线程没竞争到锁会被阻塞在外面,a线程判断实例是否为空,为空则new实例,a线程释放锁之后,b线程拿到锁进来后判断instance是否为null,此时不为null,则释放锁往下
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    // 如果实现了序列化接口,加这个方法来防止反序列化破坏单例
    public Object readResolve(){
    	return instance;
	}
}

三、getInstance字节码分析

0 getstatic #2                 // 获取静态变量instance
 3 ifnonnull 37 				// instance不为null,跳到37行
 6 ldc #3  				        // 加载类对象
 8 dup                          // 复制类对象地址
 9 astore_0                     // 存储一份类对象,解锁用
10 monitorenter                 // 进入synchronized同步代码块,创建monitor对象,monitor对象是否有owner,没有则成为owner,有则进入阻塞队列
11 getstatic #2                 // 获取静态变量instance
14 ifnonnull 27                 // instance不为null,则跳到27行
17 new #3                       // 创建instance实例对象,将对象引入栈, new Singleton()
20 dup                          // 复制instance实例对象的一个引用地址
21 invokespecial #4             // 通过复制的引用调构造方法,根据复制的引用地址调用构造方法
24 putstatic #2                 // 赋值给静态变量instance,利用一个对象引用地址,赋值给static  instance
27 aload_0					    // 加载类对象
28 monitorexit                  // 解锁
29 goto 37                      // 跳到37行
32 astore_1
33 aload_0
34 monitorexit
35 aload_1
36 athrow
37 getstatic #2    				// return instance 获取静态变量instance
40 areturn						// return instance  return结束

四、不用volatile修饰产生的问题

  • 1.当线程1执行17行new了一个实例对象的地址,执行20行dup复制了instance实例对象的地址
  • 2.线程1执行21行invokespecial,通过复制的引用地址调构造方法,由于构造方法时间过长,还未完成,指令重排,先执行24行赋为构造好的给实例对象,后调构造方法
  • 3.线程2执行到0行,获取静态变量instance,执行3行ifnonnull,由于线程1已经new出了实例对象的地址,因此ifnonnull指令的结果为非空成立,则将未构造好的对象返回了出去

五、用volatile修饰

  • 1.当线程1执行17行new了一个实例对象的地址,执行20行dup复制了instance实例对象的地址
  • 2.线程1执行21行invokespecial,通过复制的引用地址调构造方法,执行24行putstatic,是赋值写操作,会带写屏障,写屏障的作用是写操作之前的指令不会重排,即21行调用构造方法的指令不会在24行赋值指令之后执行,确保了先构造后赋值的顺序
  • 3.线程2执行到0行,获取静态变量instance,此时instance是构造好的对象,不为空,则返回
  • 4.在线程1执行完21行调用构造方法的指令之后,还未执行24行赋值指令时,线程2此时执行到0行,获取静态变量instance,此时主存中的静态变量的实例是null,则进入synchronized中阻塞等待,等线程1执行完后,线程2才能拿到锁继续判断,此时不为null,则返回该实例对象