单例模式介绍

article/2025/9/19 2:44:52

目录

1 前言

2 单例模式类型

2.1 饿汉式:

2.2 懒汉式:

2.2.1 双重检查锁

2.2.2 volatile防止指令重排

2.3 静态内部类

3 破坏单例


1 前言

单例模式是指在内存中有且只会创建一次对象的设计模式,在程序中多次使用同一个对象且作用相同的时候,为了防止频繁的创建对象,单例模式可以让程序在内存中创建一个对象,让所有的调用者都共享这一单例对象。单例模式的类型有两种:懒汉式饿汉式

2 单例模式类型

  • 饿汉式:在类加载的时候已经创建好该单例对象。
  • 懒汉式:在需要使用对象的时候才会去创建对象

2.1 饿汉式:

//饿汉式
public class Hungry {/*** 构造器私有 拒绝别人创建这个对象*/private Hungry() {}private final static Hungry HUNGRY = new Hungry();public static Hungry getInstance(){return HUNGRY;}
}

大家都知道饿汉式单例是程序启动的时候就已经创建好了对象,那么这样的会会有什么问题呢?有可能浪费空间

为什么呢?因为如果在该类中声明了许多内存空间,但却没有使用的话,就很浪费内存空间,因为饿汉式它是在程序启动的时候就已经创建好了。如下:

//饿汉式
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 byte[] data5 = new byte[1024 * 1024];/*** 构造器私有 拒绝别人创建这个对象*/private Hungry() {}private final static Hungry HUNGRY = new Hungry();public static Hungry getInstance(){return HUNGRY;}
}

那么既然饿汉式单例有问题,那么就出现了懒汉式单例模式,需要对象的时候才会去创建。

2.2 懒汉式:

//懒汉式单例
public class LazySingle {private LazySingle(){}private static LazySingle lazySingle;public static LazySingle getInstance(){if (lazySingle == null ) {lazySingle = new LazySingle();}return lazySingle;}
}

2.2.1 双重检查锁

但是这样会不会有问题呢?这样创建的懒汉式单例模式在单线程环境下肯定是没问题的,但是在多线程环境下,就会有问题了。就会不止创建一个对象了,那么如何改进呢?如下:

//懒汉式单例
public class LazySingle {private LazySingle(){System.out.println(Thread.currentThread().getName()+"ok");}private static LazySingle lazySingle;//双重检查加锁public static LazySingle getInstance(){if (lazySingle == null ){synchronized (LazySingle.class){if (lazySingle == null ) {lazySingle = new LazySingle();}}}return lazySingle;}

这就是双重检查加锁的机制了(DCL懒汉式),这样就可以保证在多线程环境下有且仅会创建一个对象。

2.2.2 volatile防止指令重排

那么这样的双重检查锁是完整的吗,会不会出现一些其他的问题呢?其实也是会的,因为当我们在 new LazySingle()的时候,其实是有可能发生指令重排的。

  • 1 分配内存空间
  • 2 执行构造方法,初始化对象
  • 3 把这个对象指向这个空间

正常情况下,执行的这个顺序是1,2,3,如果在发生了指令重排,并且在多线程的环境下,也会出现问题。比如:A线程指令重排1,3,2,那么在重排过程中线程B进来,发现lazySingle已经分配内存空间了,不等于null了,那么就直接返回了,对于这种情况应该怎样处理呢?可以使用volatile关键字来解决,如下:

//懒汉式单例
public class LazySingle {private LazySingle(){System.out.println(Thread.currentThread().getName()+"ok");}private volatile static LazySingle lazySingle;//双重检查加锁public static LazySingle getInstance(){if (lazySingle == null ){synchronized (LazySingle.class){if (lazySingle == null ) {lazySingle = new LazySingle();}}}return lazySingle;}}

2.3 静态内部类

除此之外,我们还可以使用静态内部类来实现:

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

3 破坏单例

但是无论是懒汉式单例还是饿汉式单例,都可以利用反射去破坏

    public static void main(String[] args) throws Exception {//通过正常方式获得对象LazySingle instance = LazySingle.getInstance();Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);//可访问私有构造器declaredConstructor.setAccessible(true);//利用反射构造新对象LazySingle lazySingle = declaredConstructor.newInstance();System.out.println(instance.equals(lazySingle)); //false}

以上就是利用反射强制访问类的私有构造器,去创建另外一个对象

那么如何去解决它呢?

//懒汉式单例
public class LazySingle {private LazySingle(){synchronized (LazySingle.class){if (lazySingle != null ) {throw new RuntimeException("不要利用反射去破坏单例");}}System.out.println(Thread.currentThread().getName()+"ok");}private volatile static LazySingle lazySingle;//双重检查加锁public static LazySingle getInstance(){if (lazySingle == null ){synchronized (LazySingle.class){if (lazySingle == null ) {lazySingle = new LazySingle();}}}return lazySingle;}public static void main(String[] args) throws Exception {//通过正常方式获得对象LazySingle instance = LazySingle.getInstance();Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);//可访问私有构造器declaredConstructor.setAccessible(true);//利用反射构造新对象LazySingle lazySingle = declaredConstructor.newInstance();System.out.println(instance.equals(lazySingle));}
}

控制台输出:

 以上是通过正常调用了对象和通过反射调用了一次对象发现单例被破坏了,那么如果两次直接在反射中去创建对象,这样会被发现吗?

    public static void main(String[] args) throws Exception {//通过正常方式获得对象
//        LazySingle instance = LazySingle.getInstance();Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);//可访问私有构造器declaredConstructor.setAccessible(true);//利用反射构造新对象LazySingle lazySingle = declaredConstructor.newInstance();LazySingle lazySingle1 = declaredConstructor.newInstance();System.out.println(lazySingle1.equals(lazySingle)); //false}
}

这样可见,直接这样去操作的话,单例被破坏依然是发现不了的,那么如何去解决呢?我们可以在私有构造器中加上一个标识,根据标识去判断,如下:

//懒汉式单例
public class LazySingle {private static Boolean flag = false;private LazySingle(){synchronized (LazySingle.class){if (!flag) {flag = true;}else {throw new RuntimeException("不要利用反射去破坏单例");}}System.out.println(Thread.currentThread().getName()+"ok");}private volatile static LazySingle lazySingle;//双重检查加锁public static LazySingle getInstance(){if (lazySingle == null ){synchronized (LazySingle.class){if (lazySingle == null ) {lazySingle = new LazySingle();}}}return lazySingle;}public static void main(String[] args) throws Exception {//通过正常方式获得对象
//        LazySingle instance = LazySingle.getInstance();Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);//可访问私有构造器declaredConstructor.setAccessible(true);//利用反射构造新对象LazySingle lazySingle = declaredConstructor.newInstance();LazySingle lazySingle1 = declaredConstructor.newInstance();System.out.println(lazySingle1.equals(lazySingle)); //false}
}

控制台输出:

这样就万无一失了吗?就能保证它不被破坏了吗,俗话说魔高一尺,道高一丈,反射依然可以去破坏它

    public static void main(String[] args) throws Exception {//通过正常方式获得对象
//        LazySingle instance = LazySingle.getInstance();Field flag = LazySingle.class.getDeclaredField("flag");flag.setAccessible(true);Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);//可访问私有构造器declaredConstructor.setAccessible(true);//利用反射构造新对象LazySingle lazySingle = declaredConstructor.newInstance();flag.set(lazySingle,false);LazySingle lazySingle1 = declaredConstructor.newInstance();System.out.println(lazySingle1.equals(lazySingle)); //false}

 可见啊,无论我们如何去防止它,这个反射总是可以去破解我们的单例。那究竟怎样才能解决这个问题呢?那么就得看下反射到底是如何去创建一个对象的了。

发现显示不能用反射去破坏枚举,那么真的可以这样吗,我们来试一下:

枚举类:

public enum EnumSingle {INSTANCE;public EnumSingle getInstance(){return INSTANCE;}}

 测试类:

class Test{public static void main(String[] args) throws Exception {EnumSingle instance1 = EnumSingle.INSTANCE;Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);declaredConstructor.setAccessible(true);EnumSingle instance2 = declaredConstructor.newInstance();System.out.println(instance1);System.out.println(instance2);}
}

测试结果:

可见真的不能用反射区破坏枚举。

注意:此时枚举类中不是无参构造,而是有参构造

private EnumSingle(String s,int i) {super(s,i);
}


http://chatgpt.dhexx.cn/article/t9QcYJB1.shtml

相关文章

单例模式详解(附常见的7种单例模式源码)

单例模式&#xff08;Singleton Pattern&#xff09;:保证一个类仅有一个对象&#xff0c;并提供一个访问它的全局访问点。(Ensure a class only has one instance,and provide a globe point of access to it.) 常见应用场景&#xff1a; Windows的Task Manager&#xff08;…

设计模式(一)—单例模式(附Java代码)

单例模式&#xff08;Singleton Pattern&#xff09;:采取一定的方法保证在整个的软件系统中&#xff0c;对于某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其实例对象的方法。 比如Hibernate的SessionFactory&#xff0c;它充当数据存储源的代理&#xff0c;…

线程的运行状态

不管是多线程还是多进程&#xff0c;实际上都不太可能一直占用CPU资源&#xff0c;所有多线程的几种状态一定要掌握。 多线程的状态如下图&#xff1a; 所有的系统费资源是有限的&#xff0c;不管是多线程还是多进程都必须在执行一段时间后让出资源&#xff0c;交由其他的线程…

一条SQL语句统计总数及各状态数

需求&#xff1a;共有协议X份&#xff0c;已签XX份&#xff0c;待签X份 sql: select count(1) 总记录数,sum(case when XY_STATUS1 then 1 else 0 end)待签,sum(case when XY_STATUS2 then 1 else 0 end)已签 from YG.T_ZHGL 结果&#xff1a; count(1):所有数据&#xff…

线程的执行状态

1,创建&#xff1a; 当创建好线程对象的时候&#xff0c;也就是new Thread类或者是new Thread子类的时候。此时称为创建状态 2&#xff0c;就绪&#xff1a; 当线程对象调用了start&#xff08;&#xff09;方法&#xff0c;开启线程了的时候&#xff0c;此时的线程已经开启了&…

Java线程线程的状态

1、线程的状态 线程有六种状态&#xff1a;分别如下 ① NEW(新建) 线程刚被创建&#xff0c;但是并未启动。还没调用start方法 ② Runnable(可运行) 线程可以在java虚拟机中运行的状态&#xff0c;可能正在运行自己代码&#xff0c;也可能没有&#xff0c;这取决于操作系统…

Java多线程批量执行sql

当遇到大sql批量导入时几十万上百万数据&#xff0c;使用plsql执行等都是非常的慢。因此开发一套自定义线程池处理sql&#xff1a; 1&#xff0c;线程代码 import java.util.ArrayList;/*** ClassName: com.ai.order.esb.yulang.tools.handle* Description: TODO* version: v1…

一条SQL语句是如何执行的?

大家六一儿童节好呀&#xff01; 接下来的一段时间内&#xff0c;将带领大家一同探索MySQL的奥妙&#xff0c;加油吧&#xff01;我们。 下面进入正题&#xff1a;一条SQL语句是如何进行的&#xff1f; 对于这个问题&#xff0c;我想将其分为两个问题来回答&#xff0c;分别是…

mysql 查看线程状态

show full PROCESSLIST 打开两个查询窗口&#xff0c;在A窗口执行一个查询时间较长的sql&#xff0c;在B窗口使用show full PROCESSLIST&#xff0c;可以看到A中执行的sql时间。 sleep表示没有操作&#xff0c;query表示正在查询。

线程状态总结

目录 文章目录 前言 一、线程状态图解 二、线程的几种状态 及线程过程 1.线程的几种状态 2.线程过程 总结 前言 总结了在华清远见这段时间所学的线程相关的知识点&#xff0c;文章记录了线程的几种状态及线程的过程 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案…

线程状态

原文&#xff1a;https://mp.weixin.qq.com/s/GsxeFM7QWuR--Kbpb7At2w 人类为了利用好自己的时间&#xff0c;经常会同时做多件事情&#xff0c;比如上厕所时刷手机&#xff0c;开车时听新闻... 对于自己尚且如此&#xff0c;对计算机也不能闲着。为了最大化的提升机器利用率&…

一条SQL语句的执行过程

摘要 本文站在后端开发的角度&#xff0c;讲述一条SQL从客户端发送到MySQL服务器进行处理&#xff0c;并将结果返回给客户端的过程。这个过程中涉及的操作会在后面的文章中做详细的分析。 连接建立 我们通常使用ORM框架来生成SQL语句&#xff0c;在发送SQL语句给MySQL服务器…

多线程执行sql报错处理

pymysql多线程访问数据库报错&#xff1a;Packet sequence number wrong - got 7 expected 2 原文&#xff1a;https://www.cnblogs.com/heiao10duan/p/9373237.html参考&#xff1a;https://www.jianshu.com/p/60c8e0e440ea原因&#xff1a; 使用了多线程&#xff0c;多线程共…

MySQL - 一条 SQL 语句是如何执行的(SQL执行详解)

前言 天天和数据库打交道&#xff0c;一天能写上几十条 SQL 语句&#xff0c;但你知道我们的系统是如何和数据库交互的吗&#xff1f;MySQL 如何帮我们存储数据、又是如何帮我们管理事务&#xff1f;....是不是感觉真的除了写几个 「select * from dual」外基本脑子一片空白&a…

一、SQL语句执行过程

一、MySQL架构图 MySQL逻辑架构图&#xff0c;可以分为 Server 层和存储引擎层两部分。 Server 层包括连接器、查询缓存、分析器、优化器、执行器等&#xff0c;涵盖 MySQL 的大多数核心服务功能&#xff0c;以及所有的内置函数&#xff08;如日期、时间、数学和加密函数等&…

线程状态详解

线程的生命周期 new的时候即为创建状态。 调用start即为启动了&#xff0c;启动线程后就变为就绪状态&#xff0c; 就绪之后等待CPU的调度&#xff0c;CPU调度完之后&#xff0c;就进入了运行状态。 运行状态运行sleep方法时会进入阻塞状态&#xff0c;进入阻塞状态有非常多…

一条 SQL 语句是如何执行的

1. select 语句执行过程 一条 select 语句的执行过程如上图所示 1、建立连接 连接器会校验你输入的用户名和密码是否正确&#xff0c;如果错误会返回提示&#xff0c;如果正确&#xff0c;连接器会查询当前用户对于的权限。连接器的作用就是校验用户权限 2、查询缓存 MySQL…

SQL线程状态分析:processlist

老哥哔哔叨 我们已经写了很多 MySQL 的文章了&#xff0c;比如索引优化、数据库锁、主从复制等等。今天在来和大家学习一个优化方法&#xff1a;show processlist——查看当前所有数据库连接的 session 状态。帮助我们查看每个 SQL 线程的运行状态&#xff0c;是运行正常呀&…

线程的状态

1、线程有5种状态&#xff1a;新建&#xff08;new Thread&#xff09;、就绪&#xff08;runnable&#xff09;&#xff0c;运行&#xff08;running&#xff09;、阻塞&#xff08;blocked&#xff09;、结束&#xff08;dead&#xff09; 主要方法&#xff1a; setPriorit…

基于TCP和HTTP协议的RPC简单实现

一、RPC基本概念 1、基本概念 &#xff08;1&#xff09;RPC&#xff08;Remote Procedure Call Protocol&#xff09;——远程过程调用协议&#xff0c;它是一种通过网络从远程计算机程序上请求服务&#xff0c;而不需要了解底层网络技术的协议&#xff1b; &#xff08;2&…