【学习笔记】单例模式(枚举、校验锁、volatile、反射破坏)

文章目录

      • 1. 饿汉式
      • 2. 懒汉式
      • 3. DCL 双重校验锁懒汉式
      • 4. 通过反射破坏DCL & 加锁阻止
      • 5. 通过不调用 getInstance() 来破坏单例
      • 6. 通过反射来干扰信号量,从而破坏单例
      • 7. 通过枚举类实现单例,可以防止反射破坏单例

  • 学 JUC 的时候顺便摸了下单例模式,看的是狂神的JUC教程~

1. 饿汉式

  • 缺点:可能会造成浪费空间
public class HungrySingleton {// 缺点:可能造成浪费空间(如下四行占用不必要空间)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 static HungrySingleton INSTANCE = new HungrySingleton();// 私有化构造函数private HungrySingleton() {}// 获取实例的入口public static HungrySingleton getInstance() {return INSTANCE;}public static void main(String[] args) {System.out.println(HungrySingleton.getInstance());}
}

2. 懒汉式

  • 避免了空间的浪费,单线程安全,但是多线程不安全。
public class LazySingleton {// 懒起来了,用到才实例化private static LazySingleton INSTANCE = null;private LazySingleton(){}// 单线程没事,但是多线程情况不安全public static LazySingleton getInstance(){if(INSTANCE == null){INSTANCE = new LazySingleton();}return INSTANCE;}
}

3. DCL 双重校验锁懒汉式

  • Double Check Lock,判断了两次,加了一个 synchronized 锁住代码块
  • 为什么要判断两次:可能多个进程卡在 synchronized 锁这步,所以进去后还要再判断一次
  • 不安全的原因:指令重排(见代码注释)
  • 解决方法:加 volatile 关键字禁止指令重排(见第三行注释代码)
public class DCLSingleton {private static DCLSingleton INSTANCE = null;// private volatile static DCLSingleton INSTANCE = null;private DCLSingleton(){}// 双重校验锁public static DCLSingleton getInstance(){if(INSTANCE == null){synchronized (DCLSingleton.class){if(INSTANCE == null){// 但是实际上,这一行代码还是不安全的,// 因为可能会指令重排,导致 return 未初始化完成的 INSTANCE// 解决方法是给 INSTANCE 加上一个 volatile 关键字禁止指令重排(如注释)INSTANCE = new DCLSingleton();// 然而即使如此,还是可以用反射来破坏单例模式}}}return INSTANCE;}
}
  • 从字节码指令角度,分析指令重排影响:(图源黑马JVM视频,侵删)
  • 可以看到,JIT 编译器可能会优化,先 putstatic,把引用地址赋予 INSTANCE,然后再 Method 来初始化
  • 但是,并发情况下,可能在赋予引用后,init 前,有其他线程访问了 getInstance(),获得了一个还未引用的 INSTANCE,导致出错。
    在这里插入图片描述

4. 通过反射破坏DCL & 加锁阻止

  • 可以通过反射 setAccessible 无视私有,使用构造器来破坏单例(见 main() 代码)
  • 阻止方法:在构造器再加一个 synchronized 锁,并且进行反射破坏判断并抛出异常
public class TripleSingleton {private static TripleSingleton INSTANCE = null;private TripleSingleton(){// 再加一重锁~防止反射破坏单例synchronized (TripleSingleton.class){if(INSTANCE != null){throw new RuntimeException("不要通过反射来破坏单例模式");}}}// 三重校验锁public static TripleSingleton getInstance(){if(INSTANCE == null){synchronized (TripleSingleton.class){if(INSTANCE == null){INSTANCE = new TripleSingleton();}}}return INSTANCE;}public static void main(String[] args) throws Exception{System.out.println(TripleSingleton.getInstance());// 使用反射来破坏单例模式;null 代表无参构造器Constructor<TripleSingleton> constructor = TripleSingleton.class.getDeclaredConstructor(null);// 无视私有,破坏单例模式constructor.setAccessible(true);System.out.println(constructor.newInstance());}
}

5. 通过不调用 getInstance() 来破坏单例

  • 既然 4 是基于 INSTANCE != null 的情况来判断,那么我们只要不理 INSTANCE 就可以破坏单例模式了~
  • 只通过反射得到的构造器来创造多个实例
  • 解决方法:加一个信号量 flag,在构造函数中防止这种破坏方式
public class TripleSingleton2 {private static TripleSingleton2 INSTANCE = null;// 再加一个信号量~防止破坏单例private static boolean flag = false;private TripleSingleton2(){// 再加一重锁~防止破坏单例synchronized (TripleSingleton2.class){if(flag == false){flag = true;}else {throw new RuntimeException("不要通过反射来破坏单例模式");}}}// 三重校验锁public static TripleSingleton2 getInstance(){if(INSTANCE == null){synchronized (TripleSingleton2.class){if(INSTANCE == null){INSTANCE = new TripleSingleton2();}}}return INSTANCE;}public static void main(String[] args) throws Exception{// 不调用getInstance(),直接反射多个实例的情况// 使用反射来破坏单例模式;null 代表无参构造器Constructor<TripleSingleton2> constructor = TripleSingleton2.class.getDeclaredConstructor(null);// 无视私有,破坏单例模式constructor.setAccessible(true);System.out.println(constructor.newInstance());System.out.println(constructor.newInstance());}
}

6. 通过反射来干扰信号量,从而破坏单例

  • 只要通过反射来干扰信号量,就可以继续破坏单例模式了~(见 main() 代码)
package singletons;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;/*** @Author Fwy^ ^* @Date*/
public class TripleSingleton3 {private static TripleSingleton3 INSTANCE = null;// 再加一个信号量~防止破坏单例private static boolean flag = false;private TripleSingleton3(){// 再加一重锁~防止破坏单例synchronized (TripleSingleton3.class){if(flag == false){flag = true;}else {throw new RuntimeException("不要通过反射来破坏单例模式");}}}// 三重校验锁public static TripleSingleton3 getInstance(){if(INSTANCE == null){synchronized (TripleSingleton3.class){if(INSTANCE == null){INSTANCE = new TripleSingleton3();}}}return INSTANCE;}public static void main(String[] args) throws Exception{// 使用反射来干扰信号量Field flag = TripleSingleton3.class.getDeclaredField("flag");flag.setAccessible(true);// 使用反射来破坏单例模式;null 代表无参构造器Constructor<TripleSingleton3> constructor = TripleSingleton3.class.getDeclaredConstructor(null);// 无视私有,破坏单例模式constructor.setAccessible(true);TripleSingleton3 instance1 = constructor.newInstance();flag.set(instance1, false);TripleSingleton3 instance2 = constructor.newInstance();System.out.println(instance1 + "\n" + instance2);}
}

7. 通过枚举类实现单例,可以防止反射破坏单例

  • 为了进行反射破坏,先要获取 enum 类的构造器
    在这里插入图片描述
  • 观测源码,发现有无参构造函数,然而实际上这个是假的= =。
  • 使用 javap 反编译,会发现还是有无参构造函数
  • 使用 jad,会找到一个 private EnumSingle(String s, int i) 的构造函数,成功~
public enum EnumSingleton {// 枚举实现单例,可以防止通过反射破坏单例模式~INSTANCE;public static void main(String[] args) throws Exception{System.out.println(EnumSingleton.INSTANCE);Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);System.out.println(constructor.newInstance());}
}
  • 会爆出这个错误:
  • 更加深入可以去看看 Enum 的源码~
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/504170.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

go 连接服务器 并存放图片_基于 Go 语言开发在线论坛(二):通过模型类与MySQL数据库交互...

在这篇教程中&#xff0c;我们将在 MySQL 中创建一个 chitchat 数据库作为论坛项目的数据库&#xff0c;然后在 Go 项目中编写模型类与之进行交互。你可以本地安装 MySQL 数据库&#xff0c;也可以基于 Docker 容器运行(后续会介绍容器化启动方法)。1、项目初始化开始之前&…

dax 筛选 包含某个字_DAX分享9:DAX中用变量来计算动态filter context中数值

文章写起来真的也挺麻烦的。坚持坚持&#xff01;加油加油&#xff01;本次分享的需求描述如下&#xff1a;在Power BI中创建一个页面&#xff0c;页面显示一个Table和两个Slicer。其中Slicer的内容也列在Table里。Table里需要一个计算值&#xff0c;这个计算值要求对Table中的…

c++怎么实现数字数组的删除数字_C/C++数据结构:栈结构解析,最简单解析,让你一遍就会...

上一章节针对于C语言最基本的数据结构链式结构体做了解析&#xff0c;不清楚的可以回顾一下。本章节主要针对于C语言的基础数据结构栈做以解析。数据结构之栈栈(stack)又名堆栈&#xff0c;它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶…

【总结记录】面向对象设计OOP三大特性(封装、继承、多态)、七大基本原则的理解(结合代码、现实实例理解)

文章目录一. 三大特征的理解&#xff08;1&#xff09;封装&#xff08;2&#xff09;继承&#xff08;3&#xff09;多态&#xff08;4&#xff09;面向对象、面向过程的对比二. 七大基本原则的理解&#xff08;1&#xff09;单一职责原则&#xff08;2&#xff09;开放封闭原…

arcmap shp导出cad无反应_如何使用ArcMap将Excel数据转换为shp数据

1. 概述对ArcMap而言&#xff0c;除了shapefile等数据源&#xff0c;还可以将包含地理位置的表格数据以 XY 坐标的形式添加到地图中&#xff0c;比如Excel格式的数据&#xff0c;如果包含有坐标数据在里面&#xff0c;就可以通过导入XY数据的方式将Excel数据导入&#xff0c;最…

【总结记录】《MySQL必知必会》读后笔记,结合 leetcode 例题理解

文章目录一. 《MySQL知会》读后笔记1. 零散的前文知识2. 连接数据库3. 检索数据&#xff08;重点开始了&#xff09;4. 排序、过滤数据5. 通配符、正则表达式6. 汇总数据7. 分组数据&#xff08;1&#xff09;GROUP BY&#xff08;数据分组&#xff09;&#xff08;2&#xff0…

如何使用CNN进行物体识别和分类_RCNN物体识别

R-CNN&#xff0c;图片识别目标检测(Object Detection)是图像分类的延伸&#xff0c;除了分类任务&#xff0c;还要给定多个检测目标的坐标位置。R-CNN是最早基于CNN的目标检测方法&#xff0c;然后基于这条路线依次演进出了SPPnet,Fast R-CNN和Faster R-CNN&#xff0c;然后到…

nfs服务器_Kubernetes集群下部署NFS持久存储

NFS是网络文件系统Network File System的缩写&#xff0c;NFS服务器可以让PC将网络中的NFS服务器共享的目录挂载到本地的文件系统中&#xff0c;而在本地的系统中来看&#xff0c;那个远程主机的目录就好像是自己的一个磁盘分区一样。kubernetes使用NFS共享存储有两种方式&…

c语言 指针_C 语言指针详解

(给CPP开发者加星标&#xff0c;提升C/C技能)作者&#xff1a;C语言与CPP编程 / 自成一派123(本文来自作者投稿)1为什么使用指针假如我们定义了 char a’A’ &#xff0c;当需要使用 ‘A’ 时&#xff0c;除了直接调用变量 a &#xff0c;还可以定义 char *p&a &#xff0c…

idea修改代码后不重启项目_使用DevTool实现SpringBoot项目热部署

前言最近在开发的时候&#xff0c;每次改动代码都需要启动项目&#xff0c;因为有的时候改动的服务比较多&#xff0c;所以重启的次数也就比较多了&#xff0c;想着每次重启等待也挺麻烦的&#xff0c;就打算使用DevTools工具实现项目的热部署热部署是什么大家都知道在项目开发…

c++ 单例模式_Redis单例、主从模式、sentinel以及集群的配置方式及优缺点对比

redis作为一种高效的缓存框架&#xff0c;使用是非常广泛的&#xff0c;在数据存储上&#xff0c;在运行时其将数据存储在内存中&#xff0c;以实现数据的高效读写&#xff0c;并且根据定制的持久化规则不同&#xff0c;其会不定期的将数据持久化到硬盘中。另外相较于其他的NoS…

jenkins 插件目录_10 个 Jenkins 实战经验,助你轻松上手持续集成

众所周知&#xff0c;持续构建与发布是我们日常工作中要面对的的一个重要环节&#xff0c;目前很多公司都采用 Jenkins 来搭建符合需求的 CI/CD 流程&#xff0c;作为一个持续集成的开源工具&#xff0c;它以安装启动方便&#xff0c;配置简单&#xff0c;上手容易的特点&#…

jdbc 批量insert_JDBC相关知识解答

1. JDBC_PreparedStatement插入大量数据_批处理插入_效率比较(1) jdbc新增大量数据时, 如何处理能提高效率?答&#xff1a;使用批处理提高效率(2) 什么是批处理? JDBC如何进行批处理?答&#xff1a;批处理&#xff1a;在与数据库的一次连接中&#xff0c;批量的执行条 SQL 语…

python 实现显著性检测_强!汽车车道视频检测:python+OpenCV为主实现

1 说明&#xff1a;1.1 完整版&#xff1a;汽车车道动态视频检测讲解和注释版代码&#xff0c;小白秒懂。1.2 pythonOpenCVmoviepynumpy为主的技术要点。1.3 代码来源&#xff1a;https://github.com/linghugoogle/CarND-Advanced-Lane-Lines #虽然感觉也是fork别人的&#xff…

如何学习c语言 零基础20天学会C语言

C语言开发 学习C语言不是一朝一夕的事情&#xff0c;但也不需要花费十年时间才能精通。如何以最小的代价学习并精通C语言是本文的主题。请注意&#xff0c;即使是“最小的代价”&#xff0c;也绝不是什么捷径&#xff0c;而是以最短的时间取得最多的收获&#xff0c;同时也意味…

学习C/C++的简单方法

如何学习C呢。C和C是很多专业的必修课&#xff0c;尤其对计算机专业来说&#xff0c;更是重中之重。C语言是早期发展的高级语言&#xff0c;具备执行速度快&#xff0c;语法优美等特点。是底层高效率系统的首选开发语言。今天就和大家分享一下怎么学好C/C语言吧 _ 怎么学好C、…

python数据预处理代码_Python中数据预处理(代码)

本篇文章给大家带来的内容是关于Python中数据预处理&#xff08;代码&#xff09;&#xff0c;有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你有所帮助。1、导入标准库import numpy as np import matplotlib.pyplot as plt import pandas as pd 2、…

零基础想学好C语言编程,首先要掌握的是正确的学习思路!

如果新手要学习编程&#xff0c;一些前辈都会建议从Python、PHP、Java开始学。 不过&#xff0c;有些程序员是直接从C语言强势入门编程的。 那么&#xff0c;如何学习C语言呢?下面提供4种入门C语言的方法&#xff1a; 0、刷题 绝大多数的程序员学编程的时候&#xff0c;还…

C/C++初学者快速提升?

如今&#xff0c;软件开发行业继续向前大步迈进。信息技术越来越吃香&#xff0c;越来越多人学习学习c语言&#xff0c;那么如何系统有效的学习C语言?下面分享给大家的有效学习语言的方法&#xff0c;希望可以帮到你! 一、了解大纲&#xff0c;通览教材 想学好C语言最重要的一…

pytorch 矩阵相乘_深度学习 — — PyTorch入门(三)

点击关注我哦autograd和动态计算图可以说是pytorch中非常核心的部分&#xff0c;我们在之前的文章中提到&#xff1a;autograd其实就是反向求偏导的过程&#xff0c;而在求偏导的过程中&#xff0c;链式求导法则和雅克比矩阵是其实现的数学基础&#xff1b;Tensor构成的动态计算…