文章目录

  • 饿汉模式
    • 没有保护的饿汉模式(可以被反射破坏)
    • 反射破坏普通饿汉模式
    • 带有保护的饿汉模式(构造函数加锁防反射)
    • 反射尝试破坏带有保护的饿汉模式
  • 懒汉模式
    • 基本的懒汉模式(线程不安全)
    • 基本的懒汉模式(线程安全)
      • synchronized
      • ReentrantLock
    • 双检锁(DCL)懒汉模式
    • 反射破坏带有保护的懒汉模式(构造函数加锁)
    • 反射破坏带有保护的懒汉模式(构造函数加锁 + 信号灯保护机制)
    • 反射破坏带有保护的懒汉模式(构造函数加锁 + 计数器保护机制)
  • 静态内部类
    • 静态内部类单例
    • 反射破坏静态内部类单例:
  • 枚举
    • 枚举实现单例模式
    • 反射尝试破坏枚举反射

单例模式

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

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

何时使用: 当您想控制实例数目,节省系统资源的时候

如何解决: 判断系统是否已经有这个单例,如果有则返回,如果没有则创建

关键代码: 构造函数是私有的

注意:

  • 1、单例类只能有一个实例
  • 2、单例类必须自己创建自己的唯一实例
  • 3、单例类必须给所有其他对象提供这一实例

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(例如 Spring Bean)
  • 2、避免对资源的多重占用(例如写文件操作)

缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化

注意事项: getInstance() 方法中需要使用同步锁 synchronized / Lock 防止多线程同时进入造成 instance 被多次实例化

饿汉模式

没有保护的饿汉模式(可以被反射破坏)

public class Hungry {private Hungry() {}private static final Hungry INSTANCE = new Hungry();public static Hungry getInstance() {return INSTANCE;}
}

测试:

public class SingleTest {static private final ThreadPoolExecutor POOL = new ThreadPoolExecutor(3,50,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 50; i++) {POOL.execute(() -> System.out.println(Hungry.getInstance()));}POOL.shutdown();TimeUnit.MILLISECONDS.sleep(10);}
}

这种单例模式基于classloader机制在类被装载时就被初始化了,因此不会存在多个线程调用时再产生多个实例的现象。但是就是因为类加载时就被初始化了,因此可能会导致内存的浪费

反射破坏普通饿汉模式

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<Hungry> declaredConstructor = Hungry.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);Hungry hungry1 = declaredConstructor.newInstance();Hungry hungry2 = declaredConstructor.newInstance();System.out.println(hungry1);System.out.println(hungry2);}
}

带有保护的饿汉模式(构造函数加锁防反射)

public class HungryWithProtect {private HungryWithProtect() {synchronized (HungryWithProtect.class){if(INSTANCE!=null){throw new RuntimeException("该单例已有一个实例!");}}}private static final HungryWithProtect INSTANCE = new HungryWithProtect();public static HungryWithProtect getInstance() {return INSTANCE;}
}

反射尝试破坏带有保护的饿汉模式

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<HungryWithProtect> declaredConstructor = HungryWithProtect.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);HungryWithProtect hungry1 = declaredConstructor.newInstance();HungryWithProtect hungry2 = declaredConstructor.newInstance();System.out.println(hungry1);System.out.println(hungry2);}
}

懒汉模式

基本的懒汉模式(线程不安全)

public class BaseLazy {private BaseLazy() {}private static BaseLazy instance = null;public static BaseLazy getInstance() {if (instance == null) {instance = new BaseLazy();}return instance;}
}

测试:

public class SingleTest {static private final ThreadPoolExecutor POOL = new ThreadPoolExecutor(3,50,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 50; i++) {POOL.execute(() -> System.out.println(BaseLazy.getInstance()));}POOL.shutdown();TimeUnit.MILLISECONDS.sleep(10);}
}

可以看到,在这种单例模式下,面对多个线程的操作,有可能会创建出多个实例

基本的懒汉模式(线程安全)

synchronized

public class BaseLazyWithSync {private BaseLazyWithSync() {}private static BaseLazyWithSync instance = null;public synchronized static BaseLazyWithSync getInstance() {if (instance == null) {instance = new BaseLazyWithSync();}return instance;}
}

测试:

public class SingleTest {static private final ThreadPoolExecutor POOL = new ThreadPoolExecutor(3,50,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 50; i++) {POOL.execute(() -> System.out.println(BaseLazyWithSync.getInstance()));}POOL.shutdown();TimeUnit.MILLISECONDS.sleep(10);}
}

在getInstance()方法上加了synchronized关键字,以此保证线程安全

但是synchronized太重了,在极端情况下对效率的影响还是很严重的

ReentrantLock

public class BaseLazyWithLock {private static final Lock LOCK = new ReentrantLock();private BaseLazyWithLock() {}private static BaseLazyWithLock instance = null;public static BaseLazyWithLock getInstance() {LOCK.lock();try {if (instance == null) {instance = new BaseLazyWithLock();}} catch (Exception e) {e.printStackTrace();} finally {LOCK.unlock();}return instance;}
}

测试:

public class SingleTest {static private final ThreadPoolExecutor POOL = new ThreadPoolExecutor(3,50,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 50; i++) {POOL.execute(() -> System.out.println(BaseLazyWithLock.getInstance()));}POOL.shutdown();TimeUnit.MILLISECONDS.sleep(10);}
}

ReentrantLock同样可以保证线程安全,但是会比synchronized关键字高效

双检锁(DCL)懒汉模式

public class DCLLazy {private DCLLazy() {}private volatile static DCLLazy instance = null;public static DCLLazy getInstance() {if (instance == null) {synchronized (DCLLazy.class) {if (instance == null) {instance = new DCLLazy();}}}return instance;}
}

测试:

public class SingleTest {static private final ThreadPoolExecutor POOL = new ThreadPoolExecutor(3,50,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 50; i++) {POOL.execute(() -> System.out.println(DCLLazy.getInstance()));}POOL.shutdown();TimeUnit.MILLISECONDS.sleep(10);}
}

DCL对变量instance加上了volatile关键字,并且在getInstance()方法中判空时进行了二次检测且在第二次检测时利用了synchronized关键字锁住了Class类模板来保证线程安全

反射破坏带有保护的懒汉模式(构造函数加锁)

public class LazyWithBaseProtect {private LazyWithBaseProtect() {synchronized (LazyWithBaseProtect.class) {if (INSTANCE != null) {throw new RuntimeException("该单例已有一个实例!");}}}private static LazyWithBaseProtect INSTANCE = null;public static LazyWithBaseProtect getInstance() {if (INSTANCE == null) {INSTANCE = new LazyWithBaseProtect();}return INSTANCE;}
}

测试:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {LazyWithBaseProtect instance = LazyWithBaseProtect.getInstance();System.out.println(instance);Constructor<LazyWithBaseProtect> declaredConstructor = LazyWithBaseProtect.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);LazyWithBaseProtect instance1 = declaredConstructor.newInstance();System.out.println(instance1);}
}

这种方法在已有一个实例的情况下再通过反射创建实例时会失败报错,在一定程度上可以防止反射的破坏;但是如果两个实例都是通过反射创建的话,这种方法就失效了:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<LazyWithBaseProtect> declaredConstructor = LazyWithBaseProtect.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);LazyWithBaseProtect instance1 = declaredConstructor.newInstance();System.out.println(instance1);LazyWithBaseProtect instance2 = declaredConstructor.newInstance();System.out.println(instance2);}
}

反射破坏带有保护的懒汉模式(构造函数加锁 + 信号灯保护机制)

public class LazyWithLight {private static boolean philpyOwnLight = false;private LazyWithLight() {synchronized (LazyWithLight.class) {if (!philpyOwnLight) {philpyOwnLight = true;} else {throw new RuntimeException("该单例已有一个实例!");}}}private static LazyWithLight INSTANCE = null;public static LazyWithLight getInstance() {if (INSTANCE == null) {INSTANCE = new LazyWithLight();}return INSTANCE;}
}

测试:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<LazyWithLight> declaredConstructor = LazyWithLight.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);LazyWithLight instance1 = declaredConstructor.newInstance();System.out.println(instance1);LazyWithLight instance2 = declaredConstructor.newInstance();System.out.println(instance2);}
}

看上去好像可以防止单例模式被破坏了,但是真的是这样吗

信号灯法的核心是信号灯,即上面的philpyOwnLight,这个变量是对外隐藏的;但是一旦这个变量被获得到,那么这个信号灯就又成为了摆设:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<LazyWithLight> declaredConstructor = LazyWithLight.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);Field[] declaredFields = LazyWithLight.class.getDeclaredFields();for (Field field : declaredFields) {System.out.println(field.getName() + "  " + field.getType());}}
}

通过分析getDeclaredFields()获得的值,我就可以获得信号灯的变量名(philpyOwnLight)和类型(boolean),即便信号灯设置的再复杂再隐蔽,通过反射也是可以分析出来的。知道了信号灯以后我们就又可以去破坏单例了:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {Field declaredField = LazyWithLight.class.getDeclaredField("philpyOwnLight");declaredField.setAccessible(true);Constructor<LazyWithLight> declaredConstructor = LazyWithLight.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);LazyWithLight instance1 = declaredConstructor.newInstance();declaredField.set(instance1, false);LazyWithLight instance2 = declaredConstructor.newInstance();declaredField.set(instance2, false);System.out.println(instance1);System.out.println(instance2);}
}

反射破坏带有保护的懒汉模式(构造函数加锁 + 计数器保护机制)

public class LazyWithCounter {private static int philpyCounter = 0;private LazyWithCounter() {synchronized (LazyWithCounter.class) {if (philpyCounter > 0) {throw new RuntimeException("该单例已有一个实例!");}philpyCounter++;}}private static LazyWithCounter INSTANCE = null;public static LazyWithCounter getInstance() {if (INSTANCE == null) {INSTANCE = new LazyWithCounter();}return INSTANCE;}
}

**计数器和信号灯保护机制原理都是一样的,都是通过一个标志位来标记该单例类是否已经有了一个实例对象,因此通过反射的破解方法也是大同小异
**
普通反射测试失败:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {Constructor<LazyWithCounter> declaredConstructor = LazyWithCounter.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);LazyWithCounter instance1 = declaredConstructor.newInstance();LazyWithCounter instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}


反射获取计数器对象:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {Field[] declaredFields = LazyWithCounter.class.getDeclaredFields();for (Field field : declaredFields) {System.out.println(field.getName() + "  " + field.getType());}}
}


反射破坏单例:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {Field declaredField = LazyWithCounter.class.getDeclaredField("philpyCounter");declaredField.setAccessible(true);Constructor<LazyWithCounter> declaredConstructor = LazyWithCounter.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);LazyWithCounter instance1 = declaredConstructor.newInstance();declaredField.set(instance1, 0);LazyWithCounter instance2 = declaredConstructor.newInstance();declaredField.set(instance2, 0);System.out.println(instance1);System.out.println(instance2);}
}

静态内部类

静态内部类单例

public class SIC {private SIC() {}private static class InnerClass {private static final SIC SIC = new SIC();}public static SIC getInstance() {return InnerClass.SIC;}
}

测试:

public class SingleTest {static private final ThreadPoolExecutor POOL = new ThreadPoolExecutor(3,50,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 50; i++) {POOL.execute(() -> System.out.println(SIC.getInstance()));}POOL.shutdown();TimeUnit.MILLISECONDS.sleep(10);}
}

这种方式同样利用了classloader机制来保证初始化instance时只有一个线程,并且即便外部类被装载了,instance也不一定会被初始化,因为内部类没有被主动使用;只有当显式地调用getInstance()方法时才会显示装载内部类,从而实例化instance

反射破坏静态内部类单例:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {Constructor<SIC> declaredConstructor = SIC.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);SIC instance1 = declaredConstructor.newInstance();SIC instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

同理,反射同样可以破坏构造函数加锁的静态内部类单例和信号灯保护的静态内部类单例(这里就不再演示了)

枚举

枚举实现单例模式

public enum Enum {INSTANCE;public static Enum getInstance() {return INSTANCE;}
}

测试:

public class SingleTest {static private final ThreadPoolExecutor POOL = new ThreadPoolExecutor(3,50,1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 50; i++) {POOL.execute(() -> System.out.println(Enum.getInstance()));}POOL.shutdown();TimeUnit.MILLISECONDS.sleep(10);}
}

使用枚举可以绝对保证单例模式的线程安全(尤其是可以避免通过反射来破坏单例)

反射尝试破坏枚举反射

首先看源码就可以知道枚举类是不怕反射的了:

但是我还是想试试,至少让我看到这个报错吧…

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {Constructor<Enum> declaredConstructor = Enum.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);Enum instance1 = declaredConstructor.newInstance();Enum instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

虽然出现了报错,但却并不是我们想要的源码中的那个报错,仔细看报错信息,是因为getDeclaredConstructor()方法获得构造函数时参数不对

查看idea生成的class文件:

发现这里的构造函数的确是无参的,看来时idea欺骗了我们…

那接下来用javap -c命令反编译试试:

发现构造函数有一个String类型的参数,好,再用反射试试:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {Constructor<Enum> declaredConstructor = Enum.class.getDeclaredConstructor(String.class);declaredConstructor.setAccessible(true);Enum instance1 = declaredConstructor.newInstance();Enum instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

还是不行,难道javap -c也欺骗了我们?好吧…祭出杀手锏——jad来看看:

可以看到,这里的构造函数其实是有两个参数的,分别是String和int;接下来再次用反射试试看:

public class RefSingleTest {public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {Constructor<Enum> declaredConstructor = Enum.class.getDeclaredConstructor(String.class, int.class);declaredConstructor.setAccessible(true);Enum instance1 = declaredConstructor.newInstance();Enum instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

发现了我们想要的错误,也明确了基于枚举实现的单例模式是无法通过反射来进行破坏的

单例模式与反射的攻防之【 道高一尺,魔高一丈 】相关推荐

  1. java 单例模式 泛型_设计模式之架构设计实例(工厂模式、单例模式、反射、泛型等)...

    设计模式, 架构设计实例, 使用到了工厂模式.单例模式.反射.泛型等 项目包结构如下图: 1.bean包 (1)Base.java父类 package test.bean; public class ...

  2. vlan的端口隔离及端口优化——“道高一尺魔高一丈”

    "道高一尺魔高一丈"-vlan的端口隔离及端口优化 目录:端口隔离及优化实验操作与总结 "道高一尺魔高一丈"-vlan的端口隔离及端口优化 端口隔离-制定ACL ...

  3. 如何让开源多点成功的几率;开源和 COVID-19: 道高一尺魔高一丈;等开源之道每周评论2020 04 07...

    ▼ 更多精彩推荐,请关注我们 ▼ 声明:本文所言论,仅代表适兕个人观点 文章评论 避免边缘化:开源软件如何成功? 原文链接:Avoiding the ragged edge: How open-sou ...

  4. 网站有反爬机制就爬不了数据?那是你不会【反】反爬!道高一尺魔高一丈啊!

    不知道你们在用爬虫爬数据的时候是否有发现,越来越多的网站都有自己的反爬机制,抓取数据已经不像以前那么容易,目前常见的反爬机制主要有以下几种: 数据是通过动态加载的,比如微博,今日头条,b站 需要登录, ...

  5. 爬虫与反爬虫,永恒的道高一尺魔高一丈

    打从有采集这件事情开始,就有防采集的诞生. 今天,我们就一起来说说这些年遇到的各种防采集策略. 1.限制IP单位时间访问次数还有频率 背景:没有哪个常人一秒钟内能访问相同网站N次(不管是不是同一个网页 ...

  6. 道高一尺 魔高一丈 内存泄漏智能指针

    一.什么是内存泄漏 内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果. 内存泄漏主要分为两类: 1. 堆内存泄漏 堆 ...

  7. 话说软件破解:道高一尺魔高一丈

    private string string_0 = "第1代反破解:因为序列号算法法写在软件中被你们破解,被反编译后加了你们的广告!甚至出了注册机,这是软件作者的失误."; pri ...

  8. 道高一尺魔高一丈,记强大的boost regex

    做软件免不了和各种开放的私有的协议打交道,有的时候遇到不走寻常路的protocol,那就真的不能走寻常路了,勇敢的掏出利器,让领导哭吧^_^ 言归正传,一个Kuwait项目中,client和serve ...

  9. 道高一尺 魔高一丈(使用插件订火车票)

    一,准备条件. 1,网银. 2,IE浏览器,必备.我用得时工行网银,工行就只认ie,没办法. 3,chrome浏览器.可选.这个浏览器可以方便的选择你想要的席别. 二,步骤.以使用chrome浏览器刷 ...

最新文章

  1. QIIME 2教程. 13训练特征分类器Training feature classifiers(2020.11)
  2. 资源丨机器学习进阶路上不可错过的28个视频
  3. 测试hadoop安装是否成功
  4. map/vector erase
  5. 微课|中学生可以这样学Python(例11.3):tkinter通信录管理系统4
  6. bzoj 4017: 小Q的无敌异或(线段树)
  7. SLAM会议笔记(二)Real-time DEMO
  8. 重置单例对象Singleton Swift
  9. mysql 驱动指令_Mysql的驱动包如何发送指令给MYSQL SERVER
  10. 药店零售管理php系统,医药POS零售管理系统
  11. 根据时间段自动调节pulseaudio音量
  12. 电镀用整流电源设计matlab,高功率因数的大功率开关电镀电源研究
  13. 沧海的孤塔-chimera
  14. 浏览器链接打开客户端应用
  15. 微信答题竞赛的小程序
  16. 基础C语言知识串串香10☞数组字符串结构体联合体枚举
  17. MAC苹果电脑关闭系统完整性保护SIP(System Integrity Protection)
  18. 学习SpringBoot:知乎超赞回答:Java如何进阶?分享面经
  19. springboott整合mybatis-plus和sharding-jdbc实现分库分表和读写分离(含完整项目代码)
  20. PTA-输出大写英文字母

热门文章

  1. Qt+OpenVino部署yolo5模型
  2. Uva 1151 Buy or Build 二进制枚举+最小生成树
  3. 学计算机20天培训心得体会学生,信息技术2.0培训心得总结3篇
  4. Linux 下查看硬盘 smart 信息
  5. mysql基于ssm的自习室座位管理系统 毕业设计源码221118
  6. java个十百千万位余数_1 Java第三课[流程控制]
  7. 其实你对三维设计一点也不陌生,这些竟然都属于它!
  8. 什么缩写是mzj_mzjh是什么意思,mzjh缩写代表什么意思,mzjh是什么含义
  9. 解决CCS闪退问题(亲测有效)
  10. 全触控HIFI级音质,击音Super HD II,你喜欢的样子我都有!