0.单例设计模式的学习目标
0.1掌握IDEA环境下的多线程调试方式
0.2掌握保证线程安全的单例模式策略
0.3掌握反射暴力攻击单例解决方案及原理分析
0.4掌握序列化破坏单例的原理及解决方案
0.5掌握常见的单例模式的写法
1.单例模式的定义
单例模式(Singleton Pattern) 是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
隐藏其所有的构造方法。
属于创建型模式。
2.单例模式的使用场景
确保任何情况下都绝对只有一个实例。 ServletContext、ServletConfig、ApplicationContext、DBPool
# 3.单例模式的常见写法 ## 3.1饿汉式单例 在单例类首次加载时就创建实例。
缺点:浪费内存空间
/**
* @PackageName: com.raven.pattern.singleton.hungry
* @ClassName: HungrySingleton
* @Blame: raven
* @Date: 2021-06-19 21:00
* @Description: 饿汉式单例模式
*/
public class HungrySingleton {
private HungrySingleton() {
}
private static final HungrySingleton hungrySingleton = new HungrySingleton();
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
## 3.2懒汉式单例 ### 3.2.1 简单的懒汉式单例模式
/**
* @PackageName: com.raven.pattern.singleton.lazy
* @ClassName: LazySimpleSingleton
* @Blame: raven
* @Date: 2021-06-19 22:06
* @Description: 简单的懒汉式单例模式
*/
public class LazySimpleSingleton {
private LazySimpleSingleton() {
}
private static LazySimpleSingleton lazySimpleSingleton = null;
/**
* 虽然jdk1.6版本对于synchronized进行了优化,但是在方法上直接加synchronized依旧不是最理想的懒汉式单例模式
*
* @return
*/
public static synchronized LazySimpleSingleton getInstance() {
if (lazySimpleSingleton == null) {
lazySimpleSingleton = new LazySimpleSingleton();
}
return lazySimpleSingleton;
}
}
### 3.2.2 双重检查锁懒汉式单例模式
/**
* @PackageName: com.raven.pattern.singleton.lazy
* @ClassName: LazyDoubleCheckSingleton
* @Blame: raven
* @Date: 2021-06-19 22:13
* @Description:双重检查锁懒汉式单例模式
*/
public class LazyDoubleCheckSingleton {
private LazyDoubleCheckSingleton(){};
/**
* 加volatile关键字是为了确保多线程环境下创建对象时内存可见
* 因为创建对象时 jvm会进行指令重排序,new LazyDoubleCheckSingleton 是多步操作 会有线程安全问题
*/
private static volatile LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
/**
* 将synchronized 锁范围缩小
*
* @return
*/
public static LazyDoubleCheckSingleton getInstance() {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
// 双重判断的原因是因为,假设有俩个线程A、B 如果仅仅加锁以及没有第二个if判断,
// 线程A、B可以同时进入方法并尝试获取LazyDoubleCheckSingleton锁,
// 假设A线程获取到LazyDoubleCheckSingleton锁,B线程就会进入等待状态,
// 当A线程创建完对象 但getInstance方法没有return前,B线程也会获取到锁对象,并且创建对象,从而破坏单例
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
// CPU执行时候会转换为JVM指令执行
// 指令重排序的问题
// 1.分配内存给这个对象
// 2.初始化对象
// 3.将初始化好的对象和内存地址建立关联,赋值
// 4.用户初次访问
}
}
}
return lazyDoubleCheckSingleton;
}
}
### 3.2.3 内部类的方式创建单例对象 jdk内部创建单例的方式
/**
* @PackageName: com.raven.pattern.singleton.lazy
* @ClassName: LazyInnerClassSingleton
* @Blame: raven
* @Date: 2021-06-19 22:31
* @Description: 内部类的方式创建单例对象 jdk内部创建单例的方式
*/
public class LazyInnerClassSingleton {
/**
* 单例模式类被反射所破坏 故加此代码禁止通过反射创建单例对象
*/
private LazyInnerClassSingleton() {
if (LazyHolder.singleton != null){
throw new RuntimeException("不能通过反射创建该实例对象");
}
}
/**
* 通过内部类的方式,当LazyInnerClassSingleton 的getInstance方法被调用后,
* JVM会通过自动加载内部类LazyHolder ,然后创建实例对象
* 最优的方案!
* @return
*/
public static LazyInnerClassSingleton getInstance() {
return LazyHolder.singleton;
}
private static class LazyHolder {
private static final LazyInnerClassSingleton singleton = new LazyInnerClassSingleton();
}
}
## 3.3注册式单例
package com.raven.pattern.singleton.register;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @PackageName: com.raven.pattern.singleton.register
* @ClassName: ContainerSingleton
* @Blame: raven
* @Date: 2021-06-21 21:24
* @Description: 容器式单例模式
*/
public class ContainerSingleton {
private ContainerSingleton() {
}
private static Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String className) {
synchronized (ContainerSingleton.class) {
if (!(ioc.containsKey(className))) {
Object obj = null;
try {
obj = Class.forName(className);
ioc.put(className, obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return obj;
}
}
return ioc.get(className);
}
}
## 3.4枚举式单例
package com.raven.pattern.singleton.register;
/**
* @PackageName: com.raven.pattern.singleton.register
* @ClassName: EnumSingleton
* @Blame: raven
* @Date: 2021-06-20 10:35
* @Description: 通过枚举实现注册式单例
*/
public enum EnumSingleton {
/**
* Enum的一个实例对象
*/
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
### 3.4.1枚举式单例在JDK层面防止反射破坏单例
/**
* 防止反射破坏单例设计
*
* @param args
*/
public static void main(String[] args) throws Exception {
Class clazz = EnumSingleton.class;
// 通过反编译EnumSingleton 的字节码文件发现,我们只能够通过有参构造创建EnumSingleton对象
Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
c.setAccessible(true);
// jdk内部禁止通过反射创建枚举对象
// if ((clazz.getModifiers() & Modifier.ENUM) != 0)
// throw new IllegalArgumentException("Cannot reflectively create enum objects");
// Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
EnumSingleton instance = (EnumSingleton) c.newInstance("test", 1);
EnumSingleton instance1 = EnumSingleton.getInstance();
System.out.println(instance == instance1);
}
### 3.4.2枚举式单例在JDK层面防止反序列化破坏单例
/**
* 防止反序列化破坏单例设计
*
* @param args
*/
public static void main(String[] args) throws Exception {
EnumSingleton s1 = null;
EnumSingleton s2 = EnumSingleton.getInstance();
// 将s2写到本地磁盘
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
// 将s2从磁盘读取
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (EnumSingleton) ois.readObject();
ois.close();
// 虽然我们没有重写readResolve方法,但枚举式单例仍然可以防止反序列化破坏单例设计 主要是因为枚举式通过类的class对象+名字确定返回对象 确保唯一
// Object obj = readObject0(false); ==》
// checkResolve(readEnum(unshared)); ==》
// Enum<?> en = Enum.valueOf((Class)cl, name);
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
## 3.5ThreadLocal单例
package com.raven.pattern.singleton.register;
/**
* @PackageName: com.raven.pattern.singleton.register
* @ClassName: ThreadLocalSingleton
* @Blame: raven
* @Date: 2021-06-21 21:43
* @Description: ThreadLocal线程间单例 可用于实现ORM动态切换数据源
*/
public class ThreadLocalSingleton {
private ThreadLocalSingleton() {
}
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = ThreadLocal.withInitial(ThreadLocalSingleton::new);
public static ThreadLocalSingleton getInstance() {
return threadLocalInstance.get();
}
}
4.模拟反射破坏单例问题
package com.raven.pattern.singleton.lazy;
import java.lang.reflect.Constructor;
/**
* @PackageName: com.raven.pattern.singleton.lazy
* @ClassName: ReflectBrokeSingletonTest
* @Blame: raven
* @Date: 2021-06-20 9:21
* @Description: 模拟反射破坏单例问题
*/
public class ReflectBrokeSingletonTest {
public static void main(String[] args) throws Exception {
// 虽然我们通过代码的方式实现了单例设计,但当调用者不走寻常路,使用反射是可以破坏我们单例设计的
Class<?> clazz = LazyInnerClassSingleton.class;
Constructor c = clazz.getDeclaredConstructor(null);
// 强吻
c.setAccessible(true);
// 通过反射创建的对象
Object o1 = c.newInstance();
// 通过单例设计模式获取到的对象
LazyInnerClassSingleton o2 = LazyInnerClassSingleton.getInstance();
System.out.println(o1 == o2);
// LazyInnerClassSingleton o3 = LazyInnerClassSingleton.getInstance();
// System.out.println(o2 == o3);
}
}
防止反射破坏单例
在私有构造方法中增加校验
/**
* 单例模式类被反射所破坏 故加此代码禁止通过反射创建单例对象
*/
private LazyInnerClassSingleton() {
if (LazyHolder.singleton != null){
throw new RuntimeException("不能通过反射创建该实例对象");
}
}
5.模拟反序列化破坏单例设计
package com.raven.pattern.singleton.serialiable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @PackageName: com.raven.pattern.singleton.serialiable
* @ClassName: SerializableBrokeSingletonTest
* @Blame: raven
* @Date: 2021-06-20 9:43
* @Description: 模拟反序列化破坏单例设计
*/
public class SerializableBrokeSingletonTest {
public static void main(String[] args) throws Exception {
SerializableSingleton s1 = null;
SerializableSingleton s2 = SerializableSingleton.getInstance();
// 将s2写到本地磁盘
FileOutputStream fos = new FileOutputStream("SerializableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
// 将s2从磁盘读取
FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SerializableSingleton) ois.readObject();
ois.close();
// 发现单例被反序列化所破坏 需要在单例中重写 readResolve防止序列化破坏单例
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}
package com.raven.pattern.singleton.serialiable;
import java.io.Serializable;
/**
* @PackageName: com.raven.pattern.singleton.serialiable
* @ClassName: SerializableSingleton
* @Blame: raven
* @Date: 2021-06-20 9:35
* @Description: 反序列化导致单例破坏
*/
public class SerializableSingleton implements Serializable {
// 序列化:
// 序列化就是把内存中的状态通过转换为字节码的形式
// 从而转化一个IO流,写入到其他地方(可以是磁盘,网络IO)
// 内存中的状态给永久的保存下来了
// 反序列化
// 将已经持久化的字节码内容,转换为IO流
// 通过IO流的读取,进而将读取的内容转换为Java对象
// 在转换过程中会重新创建对象new
private SerializableSingleton() {
}
private static final SerializableSingleton SINGLETON = new SerializableSingleton();
public static SerializableSingleton getInstance() {
return SINGLETON;
}
/**
* 阅读源码可知 反序列化的readObject底层原理 只需重写readResolve 即可防止反序列化对象破坏单例
* readObject ->
* readObject0 ->
* readOrdinaryObject ->
* // 对象是否已实例化,是就new一个对象出来
* obj = desc.isInstantiable() ? desc.newInstance() : null ->
* if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) ->
* readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class); ->
* // 不为空且已经实现了ReadResolve方法 就执行该方法 并返回对象
* Object rep = desc.invokeReadResolve(obj)
*
* @return
*/
private Object readResolve() {
return SINGLETON;
}
}
防止反序列化单例
在单例类中重写readResolve方法
/**
* 阅读源码可知 反序列化的readObject底层原理 只需重写readResolve 即可防止反序列化对象破坏单例
* readObject ->
* readObject0 ->
* readOrdinaryObject ->
* // 对象是否已实例化,是就new一个对象出来
* obj = desc.isInstantiable() ? desc.newInstance() : null ->
* if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) ->
* readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class); ->
* // 不为空且已经实现了ReadResolve方法 就执行该方法 并返回对象
* Object rep = desc.invokeReadResolve(obj)
*
* @return
*/
private Object readResolve() {
return SINGLETON;
}
6.总结
6.1单例设计模式的缺点
6.1.1 没有接口,拓展困难
6.1.2 如果要拓展单例对象,只有修改代码,没有其他途径。
6.2单例设计模式的优点
6.2.1 在内存中只有一个实例,减少了内存开销
6.2.2 可以避免对资源的多重占用
6.2.3 设置全局访问点,严格控制访问
6.3单例模式学习的重点
6.3.1 私有构造器
6.3.2 保证线程安全
6.3.3 延迟加载
6.3.4 防止序列化和反序列化破坏单例
6.3.5 防御反射攻击单例