Java设计模式精讲—慕课网—课程笔记4(第8章 单例模式)

8 单例模式讲解+Coding+源码解析

8.1 单例模式讲解

  1. 定义:保证一个类仅有一个实例,并提供一个全局访问点;
    类型:创建型;
  2. 适用场景:想确保任何情况下都绝对只有一个实例;
  3. 优点:
    在内存里只有一个实例,减少了内存开销;
    可以避免对资源的多重占用;
    设置全局访问点,严格控制访问;
  4. 缺点:没有接口,扩展困难;
  5. 重点:私有构造器,线程安全,延迟加载,序列化和反序列化安全,反射;
  6. 实用技能:反编译,内存原理,多线程debug;
  7. 相关设计模式:
    单例模式和工厂模式:可将工厂类设计为单例模式;
    单例模式和享元模式:结合完成单例对象的获取,类似工厂拿出已创建好的对象;

8.2 单例设计模式——懒汉式及多线程Debug实战

  1. 懒汉模式:初始化时未创建(null),构造函数为private;
    延迟加载:只有使用时才初始化;
  2. 多线程调用:
    Thread t1 = new Thread(new T());
    Thread t2 = new Thread(new T());
    t1.start;
    t2.start;
  3. 多线程debug:
    断点 -> 右键 -> Thread,控制线程执行顺序(make default:之后打的断点采用此模式);
    在下方Frames中切换线程;
  4. synchronized方法:(消耗较大)
    锁静态方法:锁整个class;
    锁非静态方法:锁堆中的对象;
    在这里插入图片描述
    在这里插入图片描述
二者效果等同

8.3 单例设计模式——DoubleCheck双重检查实战及原理解析

在这里插入图片描述

  1. 加锁保证只有一个线程能创建对象;
  2. 实际new有3个步骤:
    1) 分配内存给这个对象;
    2) 初始化对象;
    3) 指向内存地址;(2、3可能被重排序)
    Java语言规范:intra-thread semantics重排序不会改变单线程内的执行结果;
    在这里插入图片描述
单线程

在这里插入图片描述

多线程

对象未完全初始化完成,线程1可能就进行访问,发生报错;
3. 方法1:禁止2、3重排序,private volatile static,CPU共享内存;
volatile:所有线程都能看到共享内存状态,保证内存可见性;
变量写操作时会将当前处理器缓存行数据写回内存,使其他CPU里缓存的数据无效,重新从内存中读取数据;

8.4 单例设计模式——静态内部类——基于类初始化的延迟加载解决方案及原理解析

在这里插入图片描述

必须有私有构造器,否则无法保证单例

在这里插入图片描述

  1. Java中5种情况下类会立刻被初始化:
    有一个A类型实例被创建;
    A类中声明的静态方法被调用;
    A类中声明的静态成员被赋值;
    A类中声明的静态成员被使用,且不是常量成员;
    A类如果是一个顶级类,且A中有嵌套的断言语句;
    在这里插入图片描述

8.5 单例设计模式——饿汉式

  1. 优点:类加载时完成实例化,避免线程同步问题;写法简单;
    缺点:没有延迟加载效果,如果类未被使用,造成内存浪费;

在这里插入图片描述

(final变量初始化完成时就必须赋值)

在这里插入图片描述

使用静态块

8.6 单例设计模式——序列化破坏单例模式原理解析及解决方案

  1. 直接序列化、反序列化后,拿到了不同对象;
    在这里插入图片描述
  2. 在类中添加方法后返回同一对象:
    在这里插入图片描述
    否则在readOrdinaryObject()中,desc.isInstantiable()返回true,会创建一个新的对象;
    定义了readResolve()方法后,ObjectStreamClass中的hasReadResolveMethod()返回true;
    过程中其实已经实例化了新对象,但返回的是原有对象。
  3. debug源码:单步执行进入框架和源码学习。

8.7 单例设计模式——反射攻击解决方案及原理分析

1. 对饿汉式,直接反射获得构造器,失败
在这里插入图片描述
通过反射将权限设置为true,成功
在这里插入图片描述
2. 关于反射防御的代码
在私有构造器中判断
(适用于类加载时刻就将对象创建好了的方法:饿汉式、静态内部类)
在这里插入图片描述
3. 对于不在类加载时创建对象的方法:
如果仍在构造器中添加上述代码:是否破坏单例与构造顺序有关;(多线程不安全)
添加静态计数量等,在构造器中判断计数;(不安全,反射能够获得计数器权限)
在这里插入图片描述
测试:(成功禁止调用)
在这里插入图片描述
添加修改flag的代码:
在这里插入图片描述
flag方法失效。
这类方式如懒汉式、双重检查等无法避免该问题。

8.8 单例设计模式—Enum枚举单例、原理源码解析以及反编译实战

在这里插入图片描述

  1. 枚举类天然可序列化机制,能够强有力保证不会出现多次实例化;(单例模式最佳实现之一,effective java推荐)
  2. 枚举类序列化:ObjectInputStream -> readEnum()
    在这里插入图片描述
  3. 枚举类反序列化:enum只有有参构造器,且不能反射构造枚举对象;
  4. 反编译工具JAD:https://varaneckas.com/jad/

8.9 单例设计模式——容器单例

  1. 在程序初始化时,将多个单例对象放入singletonMap统一管理,在使用时通过key直接获取单例对象;
    在这里插入图片描述
  2. 多线程:初始化放置对象时可能放多个,放置完成后取时只能取到一个(同一key值后加的Object会覆盖);
  3. HashMap本身线程不安全,容器线程不安全(Map相当于缓存,统一管理节省资源);
    HashTable线程安全,容器线程安全,但会影响性能(同步锁),不建议使用;
    折中:ConCurrentHashMap(并非绝对线程安全);

8.10 单例设计模式——ThreadLocal线程单例

  1. ThreadLocal方法:不能保证单例全局唯一,能够保证线程唯一;
    在这里插入图片描述
  2. 基于ThreadLocalMap;
    ThreadLocal会给每个线程提供一个独立的变量副本;
  3. 隔离了多个线程的访问冲突,用空间换时间;

8.11 单例模式源码分析(jdk + spring + mybatis)

  1. Runtime(饿汉式),Desktop(容器单例);
  2. spring的单例:基于容器,bean作用域中的单例,应用程序上下文;
    单例设计模式:限制给定的类加载器管理的整个空间里;
    spring: AbstractFactoryBean -> getObject
  3. mybatis:ErrorContext,基于ThreadLocal保证了每个线程各自的数据;
  4. 单例模式是面试设计模式重点,应将本章中各种实现方案、优缺点、应用场景、源码使用、序列化破坏、反射攻击防御等都理解透。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值