单例模式
单例设计模式
在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
使用单例模式的好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
饿汉模式
// 饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
private Hungry(){}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式单例
package com.example.springboot_threadlocal.single;
public class LazyMan {
private static boolean flag = false;
private LazyMan() {
synchronized (LazyMan.class) {
if (!flag) {
flag = true;
} else {
if (null != lazyMan) {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
// System.out.println(Thread.currentThread().getName() + "\tOK");
}
// 加上volatile避免指令重排
private volatile static LazyMan lazyMan;
//双重检测锁模式,懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (null == lazyMan) {
lazyMan = new LazyMan();
/**
* 创建对象的三个步骤
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向这个空间
*
* A 线程 1 3 2
* B线程进来发现已经A线程中,对象已经指向了内存空间,就认为这个lazyMan不为空,直接返回,但是实际上
* 这个lazyMan没有初始化对象,会出问题
*/
}
}
}
return lazyMan;
}
}
静态内部类
package com.example.springboot_threadlocal.single;
public class Holder {
// 单例模式必须构造器私有
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
// 静态内部类方式
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
单例不安全,因为有反射
可以使用反射破坏单例
@Test
void usingClass2() throws Exception {
// 使用flag进行限制,对flag进行反射破坏
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true);
// 使用反射创建对象可能会使单例模式对象遭到破坏
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
// 都使用反射破坏
LazyMan instance1 = declaredConstructor.newInstance();
// 把instance中的flag设置为false
flag.set(instance1, false);
LazyMan instance2 = declaredConstructor.newInstance();
// 在构造方法里面没有加锁的话,这两个是会不一样的
System.out.println(instance1 == instance2);
}
枚举
枚举里面没有无参构造,只有有参构造,不能通过反射破坏单例
jad反编译枚举
jad -sjava EnumSingle.class
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.example.springboot_threadlocal.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/example/springboot_threadlocal/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
尝试使用反射破坏枚举
@Test
void test1() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 使用反编译破坏枚举单例
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle1 = declaredConstructor.newInstance();
EnumSingle enumSingle2 = declaredConstructor.newInstance();
System.out.println(enumSingle1);
System.out.println(enumSingle2);
}
显示不能使用反射创建枚举类对象
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.example.springboot_threadlocal.single.EnumSingleTest.test1(EnumSingleTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现