【创建模式-单例模式(Singleton Pattern)】

devtools/2025/2/8 5:20:33/

赐萧瑀

  • 实现方案
    • 饿汉模式
    • 懒汉式(非线程安全)
    • 懒汉模式(线程安全)
    • 双重检查锁定
    • 静态内部类
  • 攻击方式
    • 序列化攻击
    • 反射攻击
  • 枚举(最佳实践)
    • 枚举是一种类

唐 李世民
疾风知劲草,板荡识诚臣。
勇夫安识义,智者必怀仁。

实现单例模式的主要方式有:饿汉模式、懒汉模式(非线程安全)、懒汉模式(线程安全)、双重检查锁定、静态内部类和枚举方式。攻击方式有克隆攻击、序列化攻击和反射攻击。

实现方案

序号实现方式描述优点缺点
1饿汉式在类加载时就创建实例实现简单,线程安全如果实例未被使用,会造成资源浪费
2懒汉式(非线程安全)在第一次调用时创建实例延迟加载,节省资源非线程安全,多线程环境下可能创建多个实例
3懒汉式(线程安全)在第一次调用时创建实例,并使用同步方法确保线程安全延迟加载,线程安全每次调用 getInstance 都需要同步,性能较差
4双重检查锁定在第一次调用时创建实例,并使用双重检查锁定机制确保线程安全延迟加载,线程安全,且只在第一次创建实例时同步,性能较好实现较复杂,需要注意 volatile 关键字的使用
5静态内部类利用静态内部类的特性,在第一次调用时创建实例延迟加载,线程安全,实现简单无法传递参数给单例实例
6枚举使用枚举类型实现单例实现简单,线程安全,且能防止反射和序列化破坏单例不能延迟加载,且不够灵活

饿汉模式

package com.cld.designpattern.creation.singleton.hungry;import java.io.Serializable;/*** 饿汉式* 是否 Lazy 初始化:否* <p>* 是否多线程安全:是* <p>* 实现难度:易* <p>* 描述:这种方式比较常用,但容易产生垃圾对象。* 优点:没有加锁,执行效率会提高。* 缺点:类加载时就初始化,浪费内存。* 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。** @author 休克柏*/
public class HungrySingleton implements Serializable,Cloneable {private static final HungrySingleton INSTANCE = new HungrySingleton();private HungrySingleton() {if (INSTANCE != null) {throw new RuntimeException("单例构造器禁止反射调用!");}}public static HungrySingleton getInstance() {return INSTANCE;}@Overridepublic HungrySingleton clone() {//避免克隆攻击return getInstance();}
}

懒汉式(非线程安全)

import java.io.ObjectStreamException;
import java.io.Serializable;/*** (线程不安全)懒汉式* 是否 Lazy 初始化:是* * 是否多线程安全:否* * 实现难度:易* * 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加* 锁synchronized,所以严格意义上它并不算单例模式。* 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。** @author 休克柏*/
public class Lazy1Singleton implements Serializable {private static Lazy1Singleton instance;private Lazy1Singleton() {}/*** 线程不安全** @return Lazy1Singleton*/public static Lazy1Singleton getInstance() {if (instance == null) {instance = new Lazy1Singleton();}return instance;}/*** 反序列化的时候,会调用该方法,从而避免反序列化对单例的破坏* @return Lazy1Singleton* @throws ObjectStreamException*/private Object readResolve() throws ObjectStreamException {return getInstance();}
}

懒汉模式(线程安全)

package org.cqcs.knowledge.designpattern.creation.singleton.lazy;import java.io.Serializable;/*** (线程安全)懒汉式* 是否 Lazy 初始化:是* <p>* 是否多线程安全:是* <p>* 实现难度:易* <p>* 描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。* 优点:第一次调用才初始化,避免内存浪费。* 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。* getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。** @author 休克柏*/
public class Lazy2Singleton implements Serializable {private static Lazy2Singleton instance;private Lazy2Singleton() {}/*** 线程安全,但是synchronized锁比较重** @return Lazy2Singleton*/public static synchronized Lazy2Singleton getInstance() {if (instance == null) {instance = new Lazy2Singleton();}return instance;}
}

双重检查锁定

/*** JDK 版本:JDK1.5 起* * 是否 Lazy 初始化:是* * 是否多线程安全:是* * 实现难度:较复杂* * 描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。* getInstance() 的性能对应用程序很关键。* * Lazy2Singleton相对于Lazy1Singleton的效率问题,其实是为了解决1%几率的问题,* 而使用了一个100%出现的防护盾。* 那有一个优化的思路,就是把100%出现的防护盾,也改为1%的几率出现,使之只出现在可能会导致多个实例出现的地方。** @author 休克柏*/
public class DclSingleton implements Serializable {/*** volatile 的作用是对dclSingleton的写操作有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。* * volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,* 编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。* * 在java内存模型中,volatile 关键字作用可以是保证可见性或者禁止指令重排。* 这里是因为 dclSingleton = new DclSingleton() ,它并非是一个原子操作,事实上:* 在 JVM 中上述语句至少做了以下这 3 件事:* * 第一步是给 dclSingleton 分配内存空间;* * 第二步开始调用 DclSingleton 的构造函数等,来初始化 dclSingleton;* * 第三步,将 dclSingleton 对象指向分配的内存空间(执行完这步 dclSingleton 就不是 null 了)。* * 这里需要留意一下 1-2-3 的顺序,因为存在指令重排序的优化,也就是说第 2 步和第 3 步的顺序是* 不能保证的,最终的执行顺序,可能是 1-2-3,也有可能是 1-3-2。* 如果是 1-3-2,那么在第 3 步执行完以后,dclSingleton 就不是 null 了,可是这时第 2 步并没* 有执行,singleton 对象未完成初始化,它的属性的值可能不是我们所预期的值。* 假设此时线程 2 进入 getInstance 方法,由于 dclSingleton 已经不是 null 了,* * 所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错。*/private volatile static DclSingleton dclSingleton;private DclSingleton() {}public static DclSingleton getDlcSingleton() {if (dclSingleton == null) {synchronized (DclSingleton.class) {if (dclSingleton == null) {//1. 给 dclSingleton 分配内存//2. 调用 dclSingleton 的构造函数来初始化成员变量,形成实例//3. 将dclSingleton对象指向分配的内存空间(执行完这步 singleton才是非 null了)//上述3步是dclSingleton = new DclSingleton()的指令执行顺序,dclSingleton = new DclSingleton();}}}return dclSingleton;}/*** 反序列化的时候,会调用该方法,从而避免反序列化对单例的破坏* @return Lazy1Singleton* @throws ObjectStreamException*/private Object readResolve() throws ObjectStreamException {return getDlcSingleton();}
}

静态内部类

package org.cqcs.knowledge.designpattern.creation.singleton.staticinnerclass;/***静态内部类的实现方式利用了 类加载机制 和 静态内部类的特性 来保证单例的线程安全和延迟加载。** 延迟加载:* 静态内部类不会在外部类加载时立即加载,而是在第一次调用 getInstance() 方法时才会加载内部类并创建实例。这种方式实现了延迟加载,避免了资源浪费。** 线程安全:* JVM 在加载类时是线程安全的,静态内部类在加载时会由 JVM 保证线程安全,因此不需要额外的同步机制。** 静态内部类的特性:* 静态内部类是独立于外部类的,只有在被引用时才会加载。* 静态内部类的静态成员变量(单例实例)在类加载时初始化,且只会初始化一次。* @author 休克柏*/
public class StaticInnerClass {private StaticInnerClass() {}private static class SingletonHolder {private static final StaticInnerClass INSTANCE = new StaticInnerClass();}public static StaticInnerClass getInstance() {return SingletonHolder.INSTANCE;}
}

攻击方式

序列化攻击

反射攻击

枚举(最佳实践)

There are three kinds of reference types: class types, array types, and interface types. 
Their values are references to dynamically created class instances, arrays, 
or class instances or arrays that implement interfaces, respectively.

一共有三种引用类型:class types, array types, and interface types. 其值指向动态创建的类实例,数组或者分别实现了接口的类实例或者数组。
从中我们可以得知enum不是一种专门的引用数据类型,它是类。

枚举是一种类

Day.java

public enum Day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
  • 编译Day.java生成Day.class
javac Day.java
  • 反编译Day.class
javap -c Day.class
Compiled from "Day.java"
public final class demo.Day extends java.lang.Enum<demo.Day> {public static final demo.Day MONDAY;public static final demo.Day TUESDAY;public static final demo.Day WEDNESDAY;public static final demo.Day THURSDAY;public static final demo.Day FRIDAY;public static final demo.Day SATURDAY;public static final demo.Day SUNDAY;public static demo.Day[] values();Code:0: getstatic     #1                  // Field $VALUES:[Ldemo/Day;3: invokevirtual #2                  // Method "[Ldemo/Day;".clone:()Ljava/lang/Object;6: checkcast     #3                  // class "[Ldemo/Day;"9: areturn………………

从输出我们可以发现定义语句public final class demo.Day extends java.lang.Enum<demo.Day>知道我们的Day.java就是一个类,该类继承了java.lang.Enum.

/*** This is the common base class of all Java language enumeration types.** More information about enums, including descriptions of the* implicitly declared methods synthesized by the compiler, can be* found in section 8.9 of* <cite>The Java&trade; Language Specification</cite>.** <p> Note that when using an enumeration type as the type of a set* or as the type of the keys in a map, specialized and efficient* {@linkplain java.util.EnumSet set} and {@linkplain* java.util.EnumMap map} implementations are available.** @param <E> The enum type subclass* @author  Josh Bloch* @author  Neal Gafter* @see     Class#getEnumConstants()* @see     java.util.EnumSet* @see     java.util.EnumMap* @since   1.5*/
@SuppressWarnings("serial") // No serialVersionUID needed due to// special-casing of enum types.
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable {

http://www.ppmy.cn/devtools/157017.html

相关文章

RabbitMQ深度探索:消息幂等性问题

RabbitMQ 消息自动重试机制&#xff1a; 让我们消费者处理我们业务代码的时候&#xff0c;如果抛出异常的情况下&#xff0c;在这时候 MQ 会自动触发重试机制&#xff0c;默认的情况下 RabbitMQ 时无限次数的重试需要认为指定重试次数限制问题 在什么情况下消费者实现重试策略…

【CS61A 2024秋】Python入门课,全过程记录P6(Week12 Interpreters开始,更新于2025/2/7)

文章目录 关于新的问题更好的解决方案Week12Mon No Lecture: VeteransLab 10: InterpretersQ1: Using PairQ2: New ProcedureQ3: New Form 关于 个人博客&#xff0c;里面偶尔更新&#xff0c;最近比较忙。发一些总结的帖子和思考。 江湖有缘相见&#x1f91d;。如果读者想和…

IOC三种实现方式的区别

在Spring框架中&#xff0c;IOC&#xff08;控制反转&#xff09;通过依赖注入&#xff08;DI&#xff09;来实现&#xff0c;而依赖注入主要有三种实现方式&#xff1a;构造器注入、Setter注入和字段注入。每种方式都有其特点、适用场景和优缺点。以下是它们的详细对比&#x…

使用 Axios 获取用户数据并渲染——个人信息设置+头像修改

目录 功能介绍 完整源码 1. HTML 代码 2. JavaScript 代码 &#xff08;1&#xff09;获取用户信息并渲染 &#xff08;2&#xff09;头像上传 如何使用&#xff1f; 总结 本项目是一个用户个人信息管理页面&#xff0c;用于获取、修改用户信息以及更换头像。本教程详细…

大一计算机的自学总结:数据结构设计相关题

前言 说实在的&#xff0c;感觉这种设计数据结构的题比链表题还要ex&#xff0c;尤其是当哈希表和链表一起上的时候&#xff01; 一、设计有setAll功能的哈希表 #include <bits/stdc.h> using namespace std;int cnt0,setAllTime0,setAllValue; map<int,pair<in…

数据结构-队列

1.队列 1.1什么是队列 只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表称为队列&#xff0c;队列遵循先进先出FIFO&#xff08;First In First Out&#xff09;的原则。 入队列&#xff1a;进行插入操作时的一段称为队尾 出队列&#xff1a…

金蝶云星空k3cloud webapi报“java.lang.Class cannot be cast to java.lang.String”的错误

最近在对接金蝶云星空k3cloud webapi时&#xff0c;报一个莫名其妙的转换异常&#xff0c;具体如下&#xff1a; 同步部门异常! ERP接口登录异常&#xff1a;java.lang.Class cannot be cast to java.lang.String at com.jkwms.k3cloudSyn.service.basics.DeptK3CloudService.…

Vue基础:侦听器(侦听属性)【watch、watchEffect】

文章目录 引言I 侦听器(侦听属性)基本示例侦听数据源类型回调的触发时机自动停止侦听器条件式的侦听逻辑实现同步创建侦听器手动停止异步回调创建的侦听器II 侦听器选项说明一次性侦听器 once即时回调的侦听器 immediate深层侦听器 deep后置刷新 flush: post同步侦听器 flush…