设计模式--单例模式

懒加载–单例

从简入繁,一步步分析单例模式中可能存在的问题;
在这里插入图片描述

存在线程不安全的问题,如多个线程调用getInstance() 方法时,返回的并不是同一个对象;
在这里插入图片描述
在这里插入图片描述

解决方案:在getInstance()方法上添加synchronized;
在这里插入图片描述
这样的问题在于锁的力度,因为锁的是方法 那么任何情况下访问都会产生锁竞争, 但是当instance 已经实例化后,我们没必要加锁; 那么再改进一下;

在这里插入图片描述
这里要引出一个 doubleCheck的问题,由于到了synchronized会产生锁竞争,总会有其他线程在这里阻塞, 当获得锁继续执行时, 同样会执行
instance=new LazySingletonObject(); 就破坏了单例; 因为需要doubleCheck
在这里插入图片描述
到了这一步,基本可以实现单例的功能了; 但是还存在高并发下的问题, 这个问题要去字节码层面来求证;
首先补充几个知识点:

  1. 我们写的代码 底层都要翻译成字节码指令的,看似一条代码 到了底层可能会有好多层指令;
  2. 底层编译器,Cpu,Jit 都会对指令进行 指令重排序,达到更优秀的执行效率; 指令重排遵循 as-if-serial 规则;’

我们通过javap 指令查看这个类的反汇编代码,我这里只截图了必要的字节码指令,希望同学们能回去以后自己编码,查看;
在这里插入图片描述
主要的有:new 创建一个新对象 为对象分配内存空间;
补充知识点:

  1. 对象的内存分配方式: 指针碰撞, 空闲列表, TLAB(本地线程分配缓冲);
  2. 解决对象内存分配时的并发问题:CAS
    invokespecial: 根据编译时类型来调用实例方法 (执行初始化方法,初始化实例变量)
    putstatic:设置类中静态字段的值(给instance变量赋值)
    我们编写的 new LazySingletonObject();会有这3条指令, 其中 invokespecial putstatic 是有可能重排的,思考一下如果重排后会出现什么问题?
    t1 线程执行时发生了指令重排,先给instance变量赋值了, t2线程访问时instance实例不为null 直接返回,但是此时还没有完成初始化,这个实例可以使用吗? 很可能就会发生NPE(NullPointerException)异常; 所以这里要使用volatile 进行修饰,禁止指令重排;
    在这里插入图片描述
    到这里正常的懒加载单例算是完成了,但是还可以通过反射的方式来破坏;如下:
    在这里插入图片描述

饿汉式–单例模式

补充知识: 类加载机制;
类加载有以下流程: 加载,验证,准备,解析,初始化;
以下内容截取自 深入java虚拟机:
关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行 强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》 则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之 前开始):
1)遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始 化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有: ·使用new关键字实例化对象的时候。 ·读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外) 的时候。·调用一个类型的静态方法的时候。
2)使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需 要先触发其初始化。
3)当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先 初始化这个主类。
5)当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解 析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句 柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
6)当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有 这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

在这里插入图片描述
当我们调用SingletonObject.getInstance(); 会触发类加载机制,由jvm保证了线程安全; 在初始化阶段,完成了instance的赋值;

补充知识—序列化,反序列化

当我们使用序列化,反序列化得到的对象并不会相等;
在这里插入图片描述
根据Serializable中的提示,我们写一个readResolve方法,那么反序列化的时候 就会调用;
在这里插入图片描述
在这里插入图片描述
当我们添加readResolve方法后,重新序列化,反序列化, 可以看到两个实例比较为true;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值