当前位置: 首页 > news >正文

Java设计模式之单例模式以及如何防止通过反射破坏单例模式

单例模式

单例模式使用场景

​ 什么是单例模式?保障一个类只能有一个对象(实例)的代码开发模式就叫单例模式
​ 什么时候使用? 工具类!(一种做法,所有的方法都是static,还有一种单例模式让工具类只有一个实例) 某类工厂(SqlSessionFactory)

实现方式

1. 饿汉

/*** 饿汉模式(迫切加载)*/
public class Singleton01 {//构造私有化private Singleton01(){}//2 创建一个private对象private static final Singleton01 INSTANCE = new Singleton01();//这个地方就体现饿汉,一来就创建对象给他//3 提供一个static方法来获取你的这个单实例对象public static Singleton01 newInstance(){return INSTANCE;}
}

测试代码

public class MyTest {public static void main(String[] args) {Singleton01 singleton01 = Singleton01.newInstance();Singleton01 singleton02 = Singleton01.newInstance();System.out.println(singleton01.equals(singleton02));}
}

输出为true说明两个对象是相同的

2. 懒汉(懒汉太懒了,要用的时候才能创建。)

/*** 懒汉模式(懒加载) 单线程*/
public class Singleton02 {private Singleton02() {}//2 创建一个private对象private static Singleton02 INSTANCE;//3 提供一个static方法来获取你的这个单实例对象 只适合在单线程中public static Singleton02 newInstance() {if(INSTANCE == null){INSTANCE = new Singleton02()}return INSTANCE;}
}

测试代码

public class MyTest {public static void main(String[] args) {Singleton02 singleton03 = Singleton02.newInstance();Singleton02 singleton04 = Singleton04.newInstance();System.out.println(singleton03.equals(singleton04));}
}

输出为true说明两个对象是相同的
懒汉模式 只要不调用newInstance()方法 INSTANCE就一直为空 只用调用了才会创建

测试代码(如果多线程 就不是单例了)

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

运行结果
在这里插入图片描述

说明有不同的线程都调用到了构造方法
解决方法:加锁
1.方法上加锁
public static synchronized Singleton02 newInstance()
缺点:锁住整个方法 里面还有业务逻辑 效率降低很多
2.synchronized代码块

/*** 懒汉模式(懒加载)*/
public class Singleton02 {private Singleton02() {}//2 创建一个private对象private static Singleton02 INSTANCE;//3 提供一个static方法来获取你的这个单实例对象//方案1:方法锁,里面还有业务逻辑//public static synchronized  Singleton02 newInstance(){public static Singleton02 newInstance() {//方案2:锁代码块//synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队if (INSTANCE == null) {//多线程来了? T1 T2synchronized (Singleton02.class) {if (INSTANCE == null) {INSTANCE = new Singleton02();}}}//}//此处有10W行代码....return INSTANCE;}//普通方法public String getConnection() {return "I am connection!";}
}

在这里插入图片描述

只会有一个线程调用到构造方法

写到这里是不是觉得单例模式已经可以了?
但是这个代码也不是绝对安全的,不绝对是单例 利用反射去创建类的对象可以将单例进行破坏 写个测试代码

public class MyTest {
public static void main(String[] args) throws Exception{//正常获取Singleton02 instance = Singleton02.newInstance();//通过反射获取Class<? extends Singleton02> aClass = instance.getClass();//注意:构造方法是私有的Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);Singleton02 instance02 = declaredConstructor.newInstance();System.out.println(instance);System.out.println(instance02);}
}

在这里插入图片描述

生成了两个对象,说明已经通过反射将单例破坏掉了

修改代码

/*** 懒汉模式(懒加载)*/
public class Singleton02 {private Singleton02() {synchronized (Singleton02.class) {if (INSTANCE != null){throw new RuntimeException("防止反射破坏单例");}}}//2 创建一个private对象private static Singleton02 INSTANCE;//3 提供一个static方法来获取你的这个单实例对象//方案1:方法锁,里面还有业务逻辑//public static synchronized  Singleton02 newInstance(){public static Singleton02 newInstance() {//方案2:锁代码块//synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队if (INSTANCE == null) {//多线程来了? T1 T2synchronized (Singleton02.class) {if (INSTANCE == null) {INSTANCE = new Singleton02();}}}//}//此处有10W行代码....return INSTANCE;}//普通方法public String getConnection() {return "I am connection!";}
}

在这里插入图片描述

那到这一步反射就不能搞破坏了吗?
答案是可以的 为什么?
因为第一次创建的时候使用的是正常的方式肯定会调用构造方法
将第一个打印放到反射创建之前就会打印出第一个的对象 第二次通过反射再拿一个对象就不行 如果我两次都使用反射来获取对象

public class MyTest {
public static void main(String[] args) throws Exception{
Class<? extends Singleton02> aClass = Singleton02.class;//注意:构造方法是私有的Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);Singleton02 instance01 = declaredConstructor.newInstance();Singleton02 instance02 = declaredConstructor.newInstance();System.out.println(instance01);System.out.println(instance02);}
}

在这里插入图片描述

ok 单例又被破坏
还有没有其他方法?
不管是反射还是正常都会调用构造方法 那就先搞一个字段flag来进行校验

package org.jhy._01singleton;/*** 懒汉模式(懒加载)*/
public class Singleton02 {private static Boolean flag = false;private Singleton02() {
//        synchronized (Singleton02.class) {
//            if (INSTANCE != null){
//                throw new RuntimeException("防止反射破坏单例");
//            }
//        }if (flag == false) {flag = true;} else {throw new RuntimeException("防止反射破坏单例");}}//2 创建一个private对象private static Singleton02 INSTANCE;//3 提供一个static方法来获取你的这个单实例对象//方案1:方法锁,里面还有业务逻辑//public static synchronized  Singleton02 newInstance(){public static Singleton02 newInstance() {//方案2:锁代码块//synchronized (Singleton02.class){ // 100个线程哪怕有一个已经创建了也要排队if (INSTANCE == null) {//多线程来了? T1 T2synchronized (Singleton02.class) {if (INSTANCE == null) {INSTANCE = new Singleton02();}}}//}//此处有10W行代码....return INSTANCE;}//普通方法public String getConnection() {return "I am connection!";}
}
public class MyTest {
public static void main(String[] args) throws Exception{
//通过反射获取Class<? extends Singleton02> aClass = Singleton02.class;//注意:构造方法是私有的Constructor<? extends Singleton02> declaredConstructor = aClass.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);Singleton02 instance01 = declaredConstructor.newInstance();//获取字段名Field flag = aClass.getDeclaredField("flag");flag.setAccessible(true);//获取之后复位 认为又是第一次flag.set(instance01,false);Singleton02 instance02 = declaredConstructor.newInstance();System.out.println(instance01);System.out.println(instance02);}
}

在这里插入图片描述

ok,单例又被破坏了
那么反射真的就无所不能了吗???

让我们来看看newInstance()这个方法的源码
在这里插入图片描述

不能通过反射创建枚举的对象,所以用枚举就能防止反射破坏单例

public enum Singleton03 {INSTANCE;public void testMethod(){System.out.println("执行了单例类的方法");}
}// Test.java
class Test {public static void main(String[] args) {//演示如何使用枚举写法的单例类Singleton03.INSTANCE.testMethod();System.out.println(Singleton03.INSTANCE);Singleton03 instance01 = Singleton03.INSTANCE;Singleton03 instance02 = Singleton03.INSTANCE;System.out.println(instance01.equals(instance02));}
}

结果显然为true 而且枚举类里面就不能用反射的方法 枚举里面只有一个实例那就是单例

http://www.lryc.cn/news/265014.html

相关文章:

  • python flask+vue实现前后端图片上传
  • centos7安装开源日志系统graylog5.1.2
  • 5G+云渲染技术:将如何快速推进XR和元宇宙?
  • 【leetcode234】回文链表Java代码讲解
  • 指标体系构建-02-从0开始,梳理数据指标体系
  • 高速视频采集卡设计方案:620-基于PCIe的高速视频采集卡
  • MyBatis:动态 SQL 标签
  • 福建农林大学 html +css + JavaScript 期末复习 -- 保姆级
  • 推箱子小游戏
  • Spring简介
  • 万德高科携手航天科技AIRIOT打造智慧能碳管理平台, 助力碳达峰碳中和
  • 金融软件开发的 4 大挑战
  • oppo 手机刷机流程
  • SQL---数据抽样
  • C 库函数 - strxfrm()
  • 选型前必看,CRM系统在线演示为什么重要?
  • 微软官宣放出一个「小模型」,仅2.7B参数,击败Llama2和Gemini Nano 2
  • 成为一名FPGA工程师:面试题与经验分享
  • 关于“Python”的核心知识点整理大全35
  • C++ opencv RGB三通道提升亮度
  • TCAX特效字幕保姆入门教程+效果演示+软件源码自取
  • 【C语言】自定义类型:结构体深入解析(二)结构体内存对齐宏offsetof计算偏移量结构体传参
  • 活动回顾 (上) | 2023 Meet TVM 系列活动完美收官
  • JMeter常见配置及常见问题修改
  • 描述一个bug及定义bug的级别
  • Java项目-瑞吉外卖项目优化Day3
  • 测试理论知识四:大型软件的测试技巧——单元测试
  • 安防监控系统/磁盘阵列/视频监控EasyCVR平台微信推送步骤大公开
  • 算法与数据结构--特殊有序集的线性时间排序算法
  • windows 动态库和静态库 介绍