单例模式笔记

本文详细探讨了Java中的单例模式实现,包括饿汉式、懒汉式以及双检锁(DCL)策略,分析了它们在多线程环境下的行为。同时,讨论了反射机制如何破坏单例的唯一性,并展示了如何通过静态内部类和枚举来增强单例的安全性。最后,提到了反射攻击的解决方法,以及枚举作为单例模式的不可反射破坏特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

饿汉式

一开始就将对象初始化,不管用没用该类都去做分配空间,初始化对象,将对象指向空间

缺点:浪费空间

public class HungerMan {

	private HungerMan() {

	}

	private final static HungerMan hungerMan = new HungerMan();

	private static HungerMan getInstance() {
		return hungerMan;
	}

}

懒汉式

用到才去初始化对象,节约空间

缺点:只是用于单线程,程序要做并发则需要使用双检锁

public class LazyMan {
	private LazyMan() {	
	}
	
	private static LazyMan lazyMan ;
	
	private static LazyMan getInstance() {
		//为空时才去初始化对象
		if(lazyMan==null) {
			lazyMan = new LazyMan();
		}
		return lazyMan;
	}
	
	public static void main(String[] args) {
		for(int i=0;i<10;i++) {
			new Thread(()->{
				LazyMan.getInstance();
			}).start();
		}	
	}
}

双检锁 : 给上面代码增加一层锁,使用线程同步 synchronized

分析:

  • lazyMan = new LazyMan();不是原子性操作
    实际上是三个步骤:
    * 分配内存空间
    * 初始化构造方法实例对象
    * 将对象指向该空间
  • 出现指令重排
    * 启动线程可能执行123,也可能执行132
    * 如果线程A执行132,那么轮到线程B先判断对象不为null,由于对象执行顺序不为123,就会出现返回对象没有初始化
    解决方法:加volatile
public class LazyMan {
	//私有构造器
	private LazyMan() {
		System.out.println(Thread.currentThread().getName()+"ok");
	}
	
	//加上volatile 原子性操作
	private volatile static LazyMan lazyMan ;
		
	private static LazyMan getInstance() {	
		/**
		 * 使用双检锁
		 */
		if(lazyMan==null) {
			synchronized (LazyMan.class) {
				if(lazyMan==null) {
					lazyMan = new LazyMan();
				}
			}
		}				
		return lazyMan;
	}
	
	public static void main(String[] args) {
		//1.
		for(int i=0;i<10;i++) {
			new Thread(()->{
				LazyMan.getInstance();
			}).start();
		}
		
		
		
	}
}

静态内部类

静态内部类为独立层,效果与双检锁一样

package workspace.单例模式;

//静态内部类
public class Inside {
	private Inside() {
		System.out.println(Thread.currentThread().getName() + "ok");
	}
	private static Inside getInstance() {
		return Man.INSIDE;
	}
	private static class Man {
		private final static Inside INSIDE = new Inside();
	}

	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			new Thread(() -> {
				Inside.getInstance();
			}).start();
		}
	}
}

枚举与反射

除了枚举所有的方法都是不安全的,都可以使用反射去破坏,不过反射是人为去破坏的,使用双检索或者静态内部类已经足够。

反射机制

通过反射可以直接构造空参函数

public static void main(String[] args) throws Exception{
		//正常实例化
		LazyMan lazyMan2 = LazyMan.getInstance();
		System.out.println(lazyMan2);
		//反射破坏
		Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
		constructor.setAccessible(true);
		LazyMan lazyMan = constructor.newInstance();
		System.out.println(lazyMan);

 	}

两个对象结果不一样反射成功破坏
在这里插入图片描述

解决方法:给空参函数加锁

private LazyMan() {
		synchronized (LazyMan.class) {
			if(lazyMan!=null) {
				throw new RuntimeException("不要视图通过反射破坏");
			}
			
		}
	}

破解:

初始化对象两个都为反射获取空参对象
在这里插入图片描述

对抗

可加入标志密钥防止放反射
在这里插入图片描述

但是这样依然是不安全的,因为可以通过jad反编译代码知道jingQing,将其修改回false反射仍然可以破坏。

枚举不可反射破坏

通过jad反编译枚举可以发现枚举中没有空参构造函数,因此枚举不能被破坏。
编写测试代码
在这里插入图片描述

返回结果不为
在这里插入图片描述
则不是我们想要的反射破坏失败结果
去反编译

  • javap -p 反编译结果,显示我们的构造函数为有参。
    在这里插入图片描述

  • jad反编译源码中的是有参构造函数。
    在这里插入图片描述

再去破坏
在这里插入图片描述
反射不能破坏枚举单例模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liaoMITC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值