• 【java】单例模式双重检验锁


    说明
    // 1.双重检验,单例模式,懒汉式,保证线程安全
    
    • 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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    三、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结束
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    四、不用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,则返回该实例对象

  • 相关阅读:
    MySQL百万数据深度分页优化思路分析
    高级管理学:大题
    Weblogic一个domain建多个server(端口)
    七 项目管理
    ImportError: /lib64/libstdc++.so.6: version `CXXABI_1.3.9‘ not found的解决方法
    【实践成果】Splunk 9.0 Configuration Change Tracking
    T1079 计算分数加减表达式的值(信息学一本通C++)
    【mcuclub】直流电机-MX1508驱动模块
    FL Studio21.0中文版本FL水果娘发布更新
    docker 安装 Heimdall 导航页
  • 原文地址:https://blog.csdn.net/qq_32088869/article/details/128027274