JAVA单例模式代码实现

article/2025/9/19 2:27:53

JAVA常见的设计模式之单例模式

  • 懒汉模式

             懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间(搬运工)。

标准的懒汉模式

class LazySingleton {// 私有成员属性private LazySingleton lazySingleton;// 私有构造方法private LazySingleton() {}// 公共的获取实例方法public LazySingleton getLazySingleton() {// 如果成员属性为空,则创建实例if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}
}

单线程环境下,该单例模式只会有一个实例

public class TestDemo
{public static void main(String[] args) {LazySingleton lazySingleton = LazySingleton.getLazySingleton();LazySingleton lazySingleton2 = LazySingleton.getLazySingleton();System.out.println(lazySingleton == lazySingleton2);}
}

运行结果:

多线程模式下,可能会产生多个实例

public class TestDemo
{public static void main(String[] args) {new Thread(() -> {LazySingleton lazySingleton = LazySingleton.getLazySingleton();System.out.println(lazySingleton);}).start();new Thread(() -> {LazySingleton lazySingleton = LazySingleton.getLazySingleton();System.out.println(lazySingleton);}).start();}
}

运行结果:

初步改进

class LazySingleton {// 私有成员属性private static LazySingleton lazySingleton;// 私有构造方法private LazySingleton() {}// 公共的获取实例方法public synchronized static LazySingleton getLazySingleton() {// 如果成员属性为空,则创建实例if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}
}

缺点,每次调用方法都会加锁,效率低

再次改进

class LazySingleton {// 私有成员属性,使用volatile可以保证代码的有序性,防止指令重排private volatile static LazySingleton lazySingleton;// 私有构造方法private LazySingleton() {}// 公共的获取实例方法// 使用synchronized + 双重确认机制可以保证线程安全,但有可能存在指令重排,会导致创建多个实例public static LazySingleton getLazySingleton() {if (lazySingleton == null) {synchronized (LazySingleton.class) {if (lazySingleton == null) {lazySingleton = new LazySingleton();}}}return lazySingleton;}
}

静态类部类单例

/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}

 静态类不类单例不会有线程安全问题,线程安全由类加载机制担保

恶汉模式

             饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间(搬运工)。 

// 利用类加载机制保证线程安全
class HungrySingleton {private static HungrySingleton hungrySingleton = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getHungrySingleton() {return hungrySingleton;}
}

 枚举单例模式

package com.hy.test.singletonDemo;public enum EnumSingletonDemo {INSTANCE;}class EnumTest {public static void main(String[] args) {EnumSingletonDemo instance = EnumSingletonDemo.INSTANCE;EnumSingletonDemo instance2 = EnumSingletonDemo.INSTANCE;System.out.println(instance == instance2);}
}

运行结果:

单例模式可能出现的问题(都会用静态类不类单例举例)

反射攻击

/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();Class clazz = InnerSingleton.class;Constructor<InnerSingleton> declaredConstructor = clazz.getDeclaredConstructor();declaredConstructor.setAccessible(true);InnerSingleton innerSingleton1 = (InnerSingleton) declaredConstructor.newInstance();System.out.println(innerSingleton == innerSingleton1);}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}

运行结果:

由此可见,反射生成了一个新的对象,不符合单例模式的定义

解决方法:在私有构造器中添加判断,如果已存在实例对象,抛出异常(也可进行其他操作,根据需求决定)

优化后的代码如下

/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();Class clazz = InnerSingleton.class;Constructor<InnerSingleton> declaredConstructor = clazz.getDeclaredConstructor();declaredConstructor.setAccessible(true);InnerSingleton innerSingleton1 = (InnerSingleton) declaredConstructor.newInstance();System.out.println(innerSingleton == innerSingleton1);}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {// 防止反射攻击,只有恶汉与静态类部类能防止反射攻击if (InnerSingletonHolder.innerSingleton != null) {throw new RuntimeException("单例模式已存在一个实例");}}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}

 运行结果:

注意:只有恶汉模式与静态类部类能防止反射攻击

序列化相关问题

 首先,我们对创建的实例进行序列化,代码如下:

/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws IOException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();// 序列化测试ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));oos.writeObject(innerSingleton);oos.close();// 反序列化
/*        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();System.out.println(innerSingleton == innerSingleton1);*/}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {// 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,// 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败private static final long serialVersionUID = 7822769557659839582L;private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {// 防止反射攻击,只有恶汉与静态类不类能防止反射攻击if (InnerSingletonHolder.innerSingleton != null) {throw new RuntimeException("单例已存在一个实例");}}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}

然后,我们进行反序列化,查看反序列化生成的实例跟单例的实例是否是同一个

/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws IOException, ClassNotFoundException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();// 序列化测试
/*        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));oos.writeObject(innerSingleton);oos.close();*/// 反序列化ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();System.out.println(innerSingleton == innerSingleton1);}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {// 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,// 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败private static final long serialVersionUID = 7822769557659839582L;private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {// 防止反射攻击,只有恶汉与静态类不类能防止反射攻击if (InnerSingletonHolder.innerSingleton != null) {throw new RuntimeException("单例已存在一个实例");}}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}

运行结果:

由此可见,反序列化创建了一个新的实例

解决方法:Serializable的源码中给出了提示

/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws IOException, ClassNotFoundException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();// 序列化测试
/*        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));oos.writeObject(innerSingleton);oos.close();*/// 反序列化ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();System.out.println(innerSingleton == innerSingleton1);}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {// 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,// 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败private static final long serialVersionUID = 7822769557659839582L;private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {// 防止反射攻击,只有恶汉与静态类不类能防止反射攻击if (InnerSingletonHolder.innerSingleton != null) {throw new RuntimeException("单例已存在一个实例");}}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}// 反序列化时,如果是单例模式,需要重写该方法,返回单例的实例,否则会获取到不同的对象Object readResolve() throws ObjectStreamException {return InnerSingletonHolder.innerSingleton;}
}

运行结果:

因此,在工作中推荐大家使用静态类部类单例模式,可以有效的防止反射攻击与序列化带来的相关问题


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

相关文章

单例模式编写

单例">什么是单例 单例是保证一个内存/进程里只有一个类的实例&#xff0c;并提供一个访问它的全局访问点。 内存/进程中只有一个实例线程安全性能优化防止序列化产生新对象 写一个单例模式 1、饿汉模式 public class Singleton {//饿汉模式private static Single…

设计模式——单例模式八种方式实现与分析(附代码示例)

一. 概念 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法&#xff08;静态方法&#xff09;。 单例模式保证了系统内存中该类只存在一个对象&#xf…

C++ 单例模式 代码详解

单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是 最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类&#xff0c;该类负责创建自己的对象&#xff0c;同时确保只有单个对象被…

[设计模式] -- 单例模式

Emai : hahayacodergmail.com 背景 最近在公司的项目中&#xff0c;经常会用到单例模式&#xff0c;由于之前没有想过怎么正确使用单例模式&#xff0c;导致写成的程序中有BUG。在学习Cocos2d-x时&#xff0c;导演类CCDirector等都是单例类。所以从头开始学习单例模式。 介绍 …

单例模式介绍

目录 1 前言 2 单例模式类型 2.1 饿汉式&#xff1a; 2.2 懒汉式&#xff1a; 2.2.1 双重检查锁 2.2.2 volatile防止指令重排 2.3 静态内部类 3 破坏单例 1 前言 单例模式是指在内存中有且只会创建一次对象的设计模式&#xff0c;在程序中多次使用同一个对象且作用相同…

单例模式详解(附常见的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;如日期、时间、数学和加密函数等&…