单例模式的五种实现,破解以及反破解

单例模式(Singleton Pattern)

介绍

意图: 保证一个类仅有一个实例。并提供一个访问它的全局访问点。

主要解决: 一个全局使用的类频繁的创建与销毁对象。

何时使用: 一个类只需要有一个对象时。

如何解决: 判断是否已有该类实例,有则返回,无则创建。

关键代码: 类自己创建自己的实例,构造器私有化,提供全局访问点。

应用实例: 任务管理器。

注意事项: 防止多线程环境下创建多个实例对象。

饿汉式单例模式

  1. 类一加载就创建类的实例对象,即饿汉式。
  2. 只在类加载时才会初始化实例对象,故只会有一个实例。
  3. 缺点:如果只是加载该类而不需要其实例,则会造成资源的浪费。
public class SingletonDemo01 {	
	//类加载的时候就自己new实例对象
	private static SingletonDemo01 instance = new SingletonDemo01();
	
	//构造器私有化,外部不可以new对象
	private SingletonDemo01() {
	}

	//方法没有同步 调用效率高 且天然线程安全
	public static SingletonDemo01 getInstance() {
		return instance;
	}
}

懒汉式单例模式

  1. 延迟加载,懒加载。用到类实例的时候才去加载资源。
  2. 资源的利用率提高了,但getInstance方法必须加同步,并发环境下的效率降低了。
public class SingletonDemo02 {
	//类对象属性
	private static SingletonDemo02 instance;
	
	//构造器私有化
	private SingletonDemo02() {
	}

	//方法加同步 防止多线程模式下产生多个实例对象
	public static synchronized SingletonDemo02 getInstance() {
		if(instance==null) {
			instance = new SingletonDemo02();
		}		
		return instance;
	}
}

双重检测锁实现(Double Check Lock)

  1. instance = new SingletonDemo03(); 实际上有3步:

    1. 在堆内为该对象开辟一块内存空间。

    2. 初始化该对象。

    3. 该对象(instance)指向开辟的内存空间。

    而 JVM 可能会进行指令的重排,即乱序优化,从而优化内存。

    可能会出现1,3,2这样的指令顺序,而在多线程环境下就有可能访问到一个未初始化的对象。

    多线程下双重检查锁的问题及解决方法。

  2. volatile 禁止乱序优化。

  3. 第一重检测:相比于懒汉式提高效率。

    第二重检测:确保多线程环境下的单例保证。

public class SingletonDemo03 {
	//volatile 标记的变量不会使用优化功能 即1,2,3步不会乱序优化 从而避免DCL失效
	private static volatile SingletonDemo03 instance;	
	
	public static SingletonDemo03 getInstance() {
		//第一重监测
		//若该实例未创建,则加同步锁创建。若该实例存在,则直接返回
		//如果不加第一次校验的话,那跟上面的懒汉模式没什么区别,每次都要去竞争锁。
		if(instance==null) {			
			//加同步锁,
			synchronized (SingletonDemo03.class) {
				//第二重检测
				if(instance==null) {
					instance = new SingletonDemo03();
				}
			}	
		}	
		return instance;
	}
	
	//构造器的私有化
	private SingletonDemo03() {
	}
}

静态内部类实现

  1. 外部类加载时不需要立即加载内部类,就不会初始化实例 instance。

  2. 调用 getInstance 方法时才去加载内部类,进而初始化实例,类加载是线程安全的。

  3. 静态内部类实现的单例模式:兼具并发、高效调用和延迟加载的优势。

public class SingletonDemo04 {	
	//静态内部类实现
	private static class SingletonInstance{
		private static SingletonDemo04 instance = new SingletonDemo04();
	}
	
	//获取单例实例的方法
	public static SingletonDemo04 getInstance() {
		return SingletonInstance.instance;
	}
	
	//构造器私有化
	private SingletonDemo04() {
	}
}

枚举实现

  1. 枚举本身就是单例模式,由 JVM 提供保障。
  2. 避免了反射和反序列化的漏洞。由 JVM 提供保障。
  3. 缺点:没有懒加载的效果。
public enum SingletonDemo05 {	
	//这个枚举元素 本身就是单例模式
	INSTANCE;
	
	//添加自己需要的操作
	public void singletonOperation() {
		
	}
}

单例模式的破解

  1. 利用反射破解单例:直接用类的私有构造器创建实例对象。
  2. 利用反序列化破解:将实例对象序列化存储,再反序列化。
public class TestDemo06 {
	
	public static void main(String[] args) throws Exception {
		
		SingletonDemo06 instance1 = SingletonDemo06.getInstance();
		SingletonDemo06 instance2 = SingletonDemo06.getInstance();
		
		System.out.println(instance1);
		System.out.println(instance2);
		
		//通过反射的方式直接调用私有构造器
		Class<?> clazz = SingletonDemo06.class;
		Constructor<?> constructor = clazz.getDeclaredConstructor(null);
		constructor.setAccessible(true);
		SingletonDemo06 instance3 = (SingletonDemo06) constructor.newInstance(null);
		SingletonDemo06 instance4 = (SingletonDemo06) constructor.newInstance(null);
		
		System.out.println(instance3);
		System.out.println(instance4);
		
		//通过反序列化的方式破解单例模式
//		FileOutputStream fos = new FileOutputStream("resources/temp.serializable");	//序列化存入内存
//		ObjectOutputStream oos = new ObjectOutputStream(fos);	//对象输出流
//		oos.writeObject(instance2);	//单例模式的第二个对象序列化
//		oos.close();
//		fos.close();
//		
//		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("resources/temp.serializable"));
//		SingletonDemo06 instance3 = (SingletonDemo06) ois.readObject();	//从内存中反序列化
//		ois.close();
//		
//		//单例模式定义readResolve()方法
//		System.out.println(instance3);
	}
}

防止反射和反序列化的破解

  1. 防反射破解:私有构造器添加判断,第一次正常创建实例对象,而后抛异常。
  2. 防反序列化破解:定义 readResolve方法,反序列化的时候会调用这个方法。
public class SingletonDemo06 implements Serializable {
	private static final long serialVersionUID = 1L;	//序列化版本号 一旦修改则前后不兼容
		
	//类对象属性
	private static SingletonDemo06 instance;
	
	//构造器私有化
	private SingletonDemo06() {
		//防止反射漏洞 第一次正常构造 再调用构造器的话抛异常
		if(instance!=null) {
			throw new RuntimeException();
		}
	}

	//加同步 防止多线程模式下产生多个实例对象
	public static synchronized SingletonDemo06 getInstance() {
		if(instance==null) {
			instance = new SingletonDemo06();
		}
		return instance;
	}
	
	//防止反序列化的漏洞
	//基于回调 反序列化时自动调用
	//反序列化的时候直接调用此方法把整个对象返回 而不需要反序列化一个新对象
	private Object readResolve() {
		return instance;
	}
}

如何选择单例模式的实现

  • 单例对象:占用资源小,不需要延时加载

    枚举式 好于 饿汉式

  • 单例对象:占用资源大,需要延时加载

    静态内部类式 好于 懒汉式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值