【单例模式】饿汉式、懒汉式、静态内部类--简单例子

单例模式是⼀个单例类在任何情况下都只存在⼀个实例,构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例,对外提供⼀个静态公有⽅法获取实例。

目录

一、单例模式

饿汉式

 静态内部类

懒汉式

反射可以破坏单例

道高一尺魔高一丈

枚举


一、单例模式

饿汉式

类⼀加载就创建对象,这种⽅式⽐较常用,但容易产⽣垃圾对象,浪费内存空间。

// 饿汉式
public class HungryMan {private HungryMan(){}private final static HungryMan HUNGRY = new HungryMan();public static HungryMan getInstance(){return HUNGRY;}
}

那么它是如何保证线程安全的呢?它是基于类加载机制避免了多线程的同步问题,但是如果类被不同的类加载器加载就会创建不同的实例。 

优点:线程安全,没有加锁,执⾏效率较⾼。
缺点:不是懒加载,类加载时就初始化,浪费内存空间。

 静态内部类

使用静态内部类来实现单例模式是一种非常优雅且线程安全的方式。

这种方法利用了Java类加载机制的特性,即静态内部类只有在被引用时才会被加载和初始化,从而实现了延迟加载的效果。同时,由于Java虚拟机在类加载的过程中,是单线程的,所以静态内部类初始化过程中的线程安全性得到了保证。

// 静态内部类
public class Holder {private Holder(){}public static class Inner{private static final Holder HOLDER = new Holder();}public static Holder getInstance(){return Inner.HOLDER;}
}

 在这个例子中,Holder 类是我们要实现单例的类。我们将其构造函数私有化,以防止外部代码直接通过 new Holder () 来创建实例。然后,我们创建了一个私有的静态内部类 Inner ,它包含一个静态的 Holder 实例。外部代码无法直接访问这个内部类,但是可以通过调用 Holder .getInstance() 方法来获取单例实例。

Holder .getInstance() 方法被调用时,Inner 类会被加载和初始化(如果尚未加载的话),这个过程是由Java虚拟机在单线程环境中完成的,因此是线程安全的。在 Inner 类被初始化的过程中,静态变量 INSTANCE 会被创建,并且只会被创建一次。因此,Holder .getInstance() 方法总是返回同一个 Holder 实例,从而实现了单例模式。

懒汉式

懒汉式是一种单例模式的实现方式。这种方式的核心思想是延迟加载,也就是在真正需要使用到实例对象时才进行创建,而不是在程序启动时就创建。这种方式可以节省系统资源,提高程序的运行效率。

在懒汉式单例模式中,会将对象的创建过程放到一个静态方法中,并在这个方法中进行同步控制,以保证在多线程环境下仍然只创建一个实例。当程序第一次调用该方法时,会检查实例是否已经创建,如果没有,则进行创建;如果已经创建,则直接返回已经创建的实例。

// 懒汉式
public class LayMan {private LayMan(){}private static volatile LayMan layMan;// 双层检测锁模式public static LayMan getInstance(){if(layMan == null){synchronized (LayMan.class){if(layMan == null){layMan = new LayMan();  // 不是一个原子性操作:分配内存空间、执行构造方法,初始化对象、把这个对象执行空间}}}return layMan;}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{LayMan.getInstance();}).start();}}
}

那么它是如何保证线程安全呢?通过 synchronized 关键字加锁保证线程安全, synchronized 可以添加在⽅法上⾯,也可以添加在代码块上⾯,存在的问题是每⼀次调⽤ getInstance 获取实例时都需要加锁和释放锁,这样是⾮常影响性能的。

在单线程下使⽤没有问题,但是, 对于多线程是⽆法保证单例的

懒汉式单例模式的主要优点是它的延迟加载特性,可以在系统启动时减少不必要的资源消耗。然而,由于需要进行同步控制,懒汉式单例模式在多线程环境下的性能可能会受到一定影响。此外,如果实现不当,懒汉式单例模式也可能会存在线程安全问题。

---》优点:懒加载,缺点:线程不安全

但是还是不安全,可以通过反射来破坏单例。

反射可以破坏单例

反射破坏单例模式的方式主要是通过访问并调用类的私有构造函数来创建新的实例。在单例模式中,通常会将构造函数设为私有,以防止外部代码创建类的多个实例。然而,使用Java反射API,可以绕过这些访问限制。

具体来说,反射破坏单例模式的步骤如下:

  1. 获取单例类的Class对象。这可以通过调用Class.forName()方法或使用.class语法来完成。

  2. 使用Class对象的getDeclaredConstructor()方法来获取私有构造函数的Constructor对象。这个方法可以访问类中的所有构造函数,无论它们是公有还是私有。

  3. 调用Constructor.setAccessible(true)方法来允许访问私有构造函数。这将取消Java语言的访问检查,使得即使构造函数是私有的,也可以被调用。

  4. 最后,使用Constructor.newInstance()方法来创建单例类的新实例。

这样,即使单例类的构造函数是私有的,也可以通过反射来创建多个实例,从而破坏单例模式的约束。

(1)

import java.lang.reflect.Constructor;// 懒汉式
public class LayMan {private LayMan(){}private static volatile LayMan layMan;// 双层检测锁模式public static LayMan getInstance(){if(layMan == null){synchronized (LayMan.class){if(layMan == null){layMan = new LayMan();  // 不是一个原子性操作:分配内存空间、执行构造方法,初始化对象、把这个对象执行空间}}}return layMan;}public static void main(String[] args) throws Exception {LayMan instance1 = LayMan.getInstance();Constructor<LayMan> declaredConstructor = LayMan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LayMan instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

从代码运行结果可以看的,拿到了两个不同的实例: 

(2)

import java.lang.reflect.Constructor;// 懒汉式
public class LayMan {private LayMan(){}private static volatile LayMan layMan;// 双层检测锁模式public static LayMan getInstance(){if(layMan == null){synchronized (LayMan.class){if(layMan == null){layMan = new LayMan();  // 不是一个原子性操作:分配内存空间、执行构造方法,初始化对象、把这个对象执行空间}}}return layMan;}public static void main(String[] args) throws Exception {Constructor<LayMan> declaredConstructor = LayMan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LayMan instance2 = declaredConstructor.newInstance();LayMan instance3 = declaredConstructor.newInstance();System.out.println(instance3);System.out.println(instance2);}
}

从代码运行结果可以看的,拿到了两个不同的实例:  

道高一尺魔高一丈

(1)为了防止反射破坏单例模式,可以采取一些防御措施。例如,在构造函数中添加检查,如果已经存在实例,则抛出异常。

import java.lang.reflect.Constructor;// 懒汉式
public class LayMan {private LayMan(){synchronized (LayMan.class){if(layMan != null){throw new RuntimeException("不用试图使用反射破坏单例");}}}private static volatile LayMan layMan;// 双层检测锁模式public static LayMan getInstance(){if(layMan == null){synchronized (LayMan.class){if(layMan == null){layMan = new LayMan();  // 不是一个原子性操作:分配内存空间、执行构造方法,初始化对象、把这个对象执行空间}}}return layMan;}public static void main(String[] args) throws Exception {LayMan instance1 = LayMan.getInstance();Constructor<LayMan> declaredConstructor = LayMan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LayMan instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

(2)但是,简单的在构造方法里判断layMan是否是null,如果是通过反射创建的2个实例,就可以通过反射进行破坏了。

import java.lang.reflect.Constructor;// 懒汉式
public class LayMan {private LayMan(){synchronized (LayMan.class){if(layMan != null){throw new RuntimeException("不用试图使用反射破坏单例");}}}private static volatile LayMan layMan;// 双层检测锁模式public static LayMan getInstance(){if(layMan == null){synchronized (LayMan.class){if(layMan == null){layMan = new LayMan();  // 不是一个原子性操作:分配内存空间、执行构造方法,初始化对象、把这个对象执行空间}}}return layMan;}public static void main(String[] args) throws Exception {Constructor<LayMan> declaredConstructor = LayMan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LayMan instance2 = declaredConstructor.newInstance();LayMan instance3 = declaredConstructor.newInstance();System.out.println(instance3);System.out.println(instance2);}
}

从代码运行结果可以看的,拿到了两个不同的实例:  

(3)红绿灯方法:设置一个标志位flag

但是,简单的在构造方法里判断l标志位flag是否被修改过,也可以通过反射进行破坏。

import java.lang.reflect.Constructor;// 懒汉式
public class LayMan {private static boolean flag = false;private LayMan(){synchronized (LayMan.class){if(flag == false){flag = true;} else {throw new RuntimeException("不用试图使用反射破坏单例");}}}private static volatile LayMan layMan;// 双层检测锁模式public static LayMan getInstance(){if(layMan == null){synchronized (LayMan.class){if(layMan == null){layMan = new LayMan();  // 不是一个原子性操作:分配内存空间、执行构造方法,初始化对象、把这个对象执行空间}}}return layMan;}public static void main(String[] args) throws Exception {
//    LayMan instance1 = LayMan.getInstance();Constructor<LayMan> declaredConstructor = LayMan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LayMan instance2 = declaredConstructor.newInstance();LayMan instance3 = declaredConstructor.newInstance();System.out.println(instance3);System.out.println(instance2);}
}

(4)相应的,我们也可以通过反射对这个标志位flag进行修改。

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;// 懒汉式
public class LayMan {private static boolean flag = false;private LayMan(){synchronized (LayMan.class){if(flag == false){flag = true;} else {throw new RuntimeException("不用试图使用反射破坏单例");}}}private static volatile LayMan layMan;// 双层检测锁模式public static LayMan getInstance(){if(layMan == null){synchronized (LayMan.class){if(layMan == null){layMan = new LayMan();  // 不是一个原子性操作:分配内存空间、执行构造方法,初始化对象、把这个对象执行空间}}}return layMan;}public static void main(String[] args) throws Exception {
//    LayMan instance1 = LayMan.getInstance();Field f = LayMan.class.getDeclaredField("flag");f.setAccessible(true);Constructor<LayMan> declaredConstructor = LayMan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LayMan instance2 = declaredConstructor.newInstance();f.set(instance2, false);LayMan instance3 = declaredConstructor.newInstance();System.out.println(instance3);System.out.println(instance2);}
}

从代码运行结果可以看的,拿到了两个不同的实例:  

枚举

另外,还可以通过枚举类来实现单例,因为枚举类在Java中是天然的单例,且能够抵御反射攻击。

import java.lang.reflect.Constructor;public enum EnumSingle {INSTANCE;public EnumSingle getInstance(){return INSTANCE;}
}class Test{public static void main(String[] args) {EnumSingle instance1 = EnumSingle.INSTANCE;EnumSingle instance2 = EnumSingle.INSTANCE;System.out.println(instance1);System.out.println(instance2);Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true);EnumSingle instance3 = declaredConstructor.newInstance();System.out.println(instance3);}
}

从代码运行结果可以看的,拿到了两个不同的实例:  

并且也可以看到,枚举类里是没有一个无参构造方法的。

在Java中,枚举类保证单例的方式是通过Java语言规范和Java虚拟机(JVM)的内部机制来实现的。具体来说,有以下几点保证了枚举类的单例特性:

  1. 语言级别的保障:Java枚举类型在设计时就被定义为单例的。Java语言规范中明确指出,枚举常量在Java中是由静态final字段表示的,它们在第一次被使用时初始化,并且由于它们是静态的,因此只会被初始化一次。

  2. 线程安全:枚举常量的加载和初始化是由JVM在类加载的过程中完成的,这个过程是线程安全的。由于Java虚拟机提供了类加载机制的安全性,因此不需要额外的同步措施来确保枚举常量的线程安全初始化。

  3. 反射的限制:即使使用Java反射API,也无法创建枚举的额外实例。尝试通过反射来调用枚举的构造函数会抛出异常,因为JVM不允许这样做。

  4. 序列化的安全性:枚举类默认实现了Serializable接口,并且在序列化时有着特殊的处理。即使枚举实例被序列化后再反序列化,也不会创建新的实例,而是返回已经存在的枚举常量。

  5. 单一实例的保证:由于枚举类型的特性,每个枚举常量在JVM中只有一个实例,不可能通过任何方式创建出第二个相同的实例。

因此,使用枚举实现单例模式是一种非常可靠的方法,它自动处理了多线程环境下的同步问题,并且防止了反射和序列化可能导致的单例破坏。

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

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

相关文章

[html]一个动态js倒计时小组件

先看效果 代码 <style>.alert-sec-circle {stroke-dasharray: 735;transition: stroke-dashoffset 1s linear;} </style><div style"width: 110px; height: 110px; float: left;"><svg style"width:110px;height:110px;"><cir…

【GD32】_时钟架构及系统时钟频率配置

文章目录 一、有关时钟源二、系统时钟架构三、时钟树分析四、修改参数步骤1、设置外部晶振2、选择外部时钟源。3、 设置系统主频率大小4、修改PLL分频倍频系数 学习系统时钟架构和时钟树&#xff0c;验证及学习笔记如下&#xff0c;如有错误&#xff0c;欢迎指正。主要记录了总…

力扣152. 乘积最大子数组

Problem: 152. 乘积最大子数组 文章目录 题目描述思路复杂度Code 题目描述 思路 1.初始化&#xff1a;首先&#xff0c;我们创建两个数组maxNum和minNum&#xff0c;并将它们初始化为输入数组nums。这两个数组用于存储到当前位置的最大和最小乘积。我们还需要一个变量maxProduc…

python 一个点运算符操作的字典库:DottedDict

DottedDict 是一种特殊的数据结构&#xff0c;它结合了字典&#xff08;Dictionary&#xff09;和点符号&#xff08;Dot Notation&#xff09;访问的优点&#xff0c;为用户提供了一种更加直观和方便的方式来处理和访问嵌套的数据。在这篇文章中&#xff0c;我们将深入探讨 Do…

Java复习第二十天学习笔记(过滤器Filter),附有道云笔记链接

【有道云笔记】二十 4.8 过滤器Filter https://note.youdao.com/s/dSofip3f 一、为什么要使用过滤器 项目开发中&#xff0c;经常会用到重复代码的实现。 1、请求每个servlet都要设置编码 2、判断用户是否登录&#xff0c;只有登录了才有操作权限。 二、过滤器相关Api int…

从汇编代码理解数组越界访问漏洞

数组越界访问漏洞是 C/C 语言中常见的缺陷&#xff0c;它发生在程序尝试访问数组元素时未正确验证索引是否在有效范围内。通常情况下&#xff0c;数组的索引从0开始&#xff0c;到数组长度减1结束。如果程序尝试访问小于0或大于等于数组长度的索引位置&#xff0c;就会导致数组…

windows关闭Windows Search功能

我发现windows最恶心的功能就是自动更新和搜索。自动更新就是个毒瘤&#xff0c;得到了全世界的人讨厌。 而搜索功能难用、慢和造成卡死&#xff0c;根本没有存在的必要。并且他的windows search filter服务会在每次移动大量文件后建立索引&#xff0c;持续的占用cpu和硬盘的资…

python解释器安装路径查询以及版本查询

查询安装路径 1、利用脚本&#xff1a; 路径: import sys import osprint(当前 Python 解释器路径&#xff1a;) print(sys.executable)运行结果&#xff1a; 目录: print(当前 Python 解释器目录&#xff1a;) print(os.path.dirname(sys.executable))运行结果&#xff1a…

static+单例模式+类的复合继承

汇编语言 汇编语言是最靠谱的验证“编程语言相关知识点”正确性的方式 汇编语言与机器语言一一对应&#xff0c;每一条机器语言都有与之对应的汇编指令 机器语言是计算机使用的语言&#xff0c;它是一串二进制数字 汇编语言可以通过汇编得到机器语言机器语言可以通过反汇编得到…

设计模式—门面模式

定义: 门面模式,也称为外观模式&#xff0c;是一种结构型设计模式。它的主要目的是提供统一的接口来访问子系统中的多个接口&#xff0c;从而简化客户端与复杂子系统之间的交互。 在门面模式中&#xff0c;一个门面类充当中介&#xff0c;为客户端提供一个简化了的访问方式&…

基于Adaboost模型的数据预测和分类matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 AdaBoost&#xff08;Adaptive Boosting&#xff09;是一种集成学习方法&#xff0c;由Yoav Freund和Robert Schapire于1995年提出&#xff0c;主要用于提高弱分类…

【echarts】使用 ECharts 绘制3D饼图

使用 ECharts 绘制3D饼图 在数据可视化中&#xff0c;饼图是表达数据占比信息的常见方式。ECharts 作为一个强大的数据可视化库&#xff0c;除了标准的二维饼图&#xff0c;也支持更加生动的三维饼图绘制。本文将指导你如何使用 ECharts 来创建一个3D饼图&#xff0c;提升你的…

VScode配置launch+tasks[自己备用]

VScode配置launchtasks[自己备用]&#xff0c;配置文件详解 launch.json 字段 name &#xff1a;启动配置的名称&#xff0c;也就是显示在调试配置下拉菜单中的名字&#xff0c;如果添加了多个配置可以用此作为区分 字段 program &#xff1a;可执行文件完整路径。 ① 由于 C…

ARP代理

10.1.0.1/8 和10.2.0.1/8是在同一个网段 10.1.0.2/16 和10.2.0.2/16 不在同一个网段 10.1.0.1/8 和10.1.0.2/16 是可以ping通的 包发出来了&#xff0c;报文有发出来&#xff0c;目的地址是广播包 广播请求&#xff0c;发到路由器的接口G 0/0/0 target不是本接口&#xff0…

利用redis和fastapi实现本地与平台策略进行交互

redis在pandas一文有详细使用方法(一文教会pandas-CSDN博客)&#xff0c;具体可视化软件有redisstudio等。它是一个由 Salvatore Sanfilippo 写的 key-value 存储系统&#xff0c;是跨平台的非关系型数据库。 Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络…

【教程】ubuntu20.04 下配置 Charm-crypto 0.5 实验环境

目录 前言先决条件基本依赖安装准备好 gcc&#xff0c;make 和 perl准备好 m4&#xff0c;flex&#xff0c;bison 和 libssl-dev安装 Python3.x&#xff0c;pip3 和 pyparsing 安装 OpenSSL安装 GMP5.x安装 PBC安装 Charm-crypto5.0安装开发环境检验 Charm-crypto5.0 安装成功参…

【【相机运动】_Camera_shake镜头晃动动画】

【相机运动】:Camera shake镜头晃动动画 2022-07-20 20:28 评论(0)

OpenCV轻松入门(八)——图片卷积

对图像和滤波矩阵进行逐个元素相乘再求和的操作就相当于将一个二维的函数移动到另一个二维函数的所有位置&#xff0c;这个操作就叫卷积。 卷积需要4个嵌套循环&#xff0c;所以它并不快&#xff0c;除非我们使用很小的卷积核。这里一般使用3x3或者5x5 图像滤波 图像滤波是尽…

怎么样在外网登录访问CRM管理系统?

一、什么是CRM管理系统&#xff1f; Customer Relationship Management&#xff0c;简称CRM&#xff0c;指客户关系管理&#xff0c;是企业利用信息互联网技术&#xff0c;协调企业、顾客和服务上的交互&#xff0c;提升管理服务。为了企业信息安全以及使用方便&#xff0c;企业…

智能零售:引领购物新时代

智能零售通过整合人工智能、物联网、大数据和机器学习等技术&#xff0c;正在彻底改变传统的购物模式&#xff0c;为消费者和零售商提供前所未有的效率和个性化体验。 智能零售利用消费者数据分析来提供个性化的购物推荐。无论是在线平台或是实体店内&#xff0c;智能系统都能…