- 单例模式:提供一种共享功能需求。确保一个类只有一个实例,并提供该实例的全局访问点。
一、场景分析
- 设计需求:任何地方访问该实例都是同一个实例,实现数据的共享需求&节约资源
- 注意点:
- 单例类只能有一个实例(唯一性)
- 单例类必须自己创建自己的唯一实例(主动性)
- 单例类必须个所有其他对象提供这一实例(共享性)
- 设计实现:构造函数是私有的(使得无法new新对象)
- 优点:
- 节省内存开销(只有一个实例,且避免了频繁的创建和销毁实例)
- 避免对资源的多重占用,如写文件操作
- 缺点:
- 没有接口,不能继承
- 与单一责任原则冲突:一个类应只关心内部逻辑,而不关心外部怎样来实例化(单例限制了构造函数)
- 使用场景:
- 要求生产唯一序列化
- Web中的计数器,不用每次刷新都完数据库加一次,用单例缓存起来
- 节约资源。创建一个对象需要消耗资源过多,如I/O与数据库的连接等
- 应用实例:
- 多线程多进程处理一个文件的现象,通过唯一实例进行
- 设备管理器,如一个电脑有两个打印机,但输出时不能是两台打印机打印
![在这里插入图片描述](https://img-blog.csdnimg.cn/64d486e22c8a404c96a67b4b0440a368.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpZWRvbmd6ZV9f,size_16,color_FFFFFF,t_70#pic_center)
一、单例模式实现
1. 懒汉式-线程不安全
- 实现:延迟实例化
- 优点:延迟加载
- 缺点:线程不安全
- 线程不安全情况:可能多个线程同时实例化,导致创建多个实例
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
2. 饿汉式-线程安全
private static Singleton uniqueInstance = new Singleton();
3. 懒汉式-线程安全
- 实现:在延迟实例化上加synchronzed锁,保证同时只能有一个线程进行实例化操作
- 优点:线程安全、延迟加载
- 缺点:性能不佳
- 多线程同时进入时其他线程被阻塞,即使对象已经被实例化。
- 这会让线程阻塞时间过长,该方法存在性能问题,不推荐使用。
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
4. 双重校验锁-线程安全
- 实现:
- 将synchronized锁的范围缩小
- synchronized内增加多一个if判断
- 使用volatile修饰私有变量对象实例
- 对象实例化步骤:
- 为对象分配内存空间
- 初始化对象
- 将对象变量指向分配的内存地址
- JVM具有指令重排特性,多线程环境下可能变成1-3-2的顺序,导致一个线程获得还【没有初始化的实例】
- 如只有外层一个if:增加多一个if后可使得后续并发线程获取锁后,直接判断if后跳过实例化,从而实现单例。
- 如两个线程同时进入第一个if,然后发生锁竞争,另一个线程等待。当第一个线程实例化后,第二个线程依然会实例化。
- 优点:延迟加载、线程安全
public class Singleton {
//volatile禁止重排序+内存可见性
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
//缩小synchronized范围,减少阻塞优化性能
synchronized (Singleton.class) {
//避免多个线程同时判断第一个if进入锁争用导致线程不安全
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
5. 静态内部类实现
- 实现:利用JVM——>类的加载机制——>类的初始化时机——>被动引用
- 个人分析:妙!
- 静态内部类的引用不会在父类被加载时也加载进内存
- JVM保证了类只会加载一次
- static属于类的成员变量,又保证了该变量也只会被初始化一次
- 使用static使得该变量为内部类的变量,从而实现唯一变量
- final字段可实现单例模式的不可变
- 因为是内部类,所以可以使用外部类的public方法实现安全访问
- 优点:延迟加载、线程安全
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
6. 枚举实现
- 实现:通过枚举类型实现
- 优点:线程安全、防止反射攻击&序列攻击
public enum Singleton {
INSTANCE;
private String objName;
public String getObjName() {
return objName;
}
public void setObjName(String objName) {
this.objName = objName;
}
public static void main(String[] args) {
// 单例测试
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName("firstName");
System.out.println(firstSingleton.getObjName());
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName("secondName");
System.out.println(firstSingleton.getObjName());
System.out.println(secondSingleton.getObjName());
// 反射获取实例测试
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Enum防反射攻击:反射实现的Constructor限制了ENUM类型:Cannot reflectively create enum objects
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
//这里判断Modifier.ENUM是不是枚举修饰符,如果是就抛异常
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
Enum防序列化攻击:使用transient禁止enumConstantDirectory序列化,而通过反射的方式调用枚举类的values方法
Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
//getEnumConstantsShared最终通过反射调用枚举类的values方法
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map<String, T> m = new HashMap<>(2 * universe.length);
//map存放了当前enum类的所有枚举实例变量,以name为key值
for (T constant : universe)
m.put(((Enum<?>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}
private volatile transient Map<String, T> enumConstantDirectory = null;
二、单例模式攻击
1. 防反射攻击——单例模式
- 反射攻击:通过setAccessible()方法改变私有构造函数的访问级别,进而再次初始化破坏“单例”的特性。
反射攻击:未添加防反射代码逻辑将显示:攻击Sinleton成功
public static void attackSingleton() {
Class singletonClass = Test.class;
try {
Constructor[] constructors = singletonClass.getDeclaredConstructors();
constructors[0].setAccessible(true);
Test instance1 = (Test) constructors[0].newInstance();
Test instance2 = (Test) constructors[0].newInstance();
if (instance1 == instance2) {
System.out.println("attack failed,攻击Sinleton失败");
} else {
System.out.println("attack success!攻击Sinleton成功,创建了多个实例");
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
防反射攻击方式
- 在私有构造函数中增加额外判断
- 适用:先获取单例再反射构造
- 不适用:先反射构建再获取单例,此时if判断会被跳过
- 使用枚举Enum创建单例模式
防反射攻击:构造函数增加额外判断,此时反射改为public也需要进行if判断
private Singleton() {
//如果已存在,直接抛出异常,保证只会被new 一次
// if(InnerClass.INSTANCE != null){
if (INSTANCE != null) {
throw new RuntimeException("对象已存在不可重复创建");
}
}
2. 防序列化攻击——单例模式
- 序列化攻击:通过多次序列化和反序列使得变成单例实例变成多个实例
单例模式序列化测试:攻击成功,多个实例
DoubleLock doubleLock = DoubleLock.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("doubleLock_obj"));
oos.writeObject(doubleLock);
File file = new File("doubleLock_obj");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
DoubleLock newDoubleLock = (DoubleLock) ois.readObject();
System.out.println(doubleLock);
System.out.println(newDoubleLock);
System.out.println(doubleLock == newDoubleLock); //结果为false,说明为两个对象
防序列化攻击实现:在类中增加readResolve()方法
private Object readResolve(){
return Singleton;
}
防序列化攻击底层:ObjectInputStream底层源码将返回一个新实例
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
......
Object obj;
try {
//当实现了Serializable接口时desc.isInstantiable()返回true,会创建一个新实例
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
......
}
防序列化攻击底层:ObjectStreamClass中进行了逻辑判断,当实例化的类定义了readResolve方法时将不会再实例化一个方法
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
三、总结
线程安全
比较
- 支持延迟加载:双重校验锁、静态内部类
- (天然)防止反射攻击:枚举实现
- (天然)防止序列化攻击:枚举实现
例子
- Logger Classes
- Configuration Classes
- Accesing resources in shared mode
- Factories implemented as Singletons
JDK
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
引用
单例模式安全之反射攻击
防反射攻击
深入理解Java枚举类型(enum)
单例(Singleton)
Java单例—序列化破坏单例模式原理解析