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

news2025/2/7 20:31:39

赐萧瑀

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

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

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

实现方案

序号实现方式描述优点缺点
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;
    }
 
    @Override
    public 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.coloradmin.cn/o/2294457.html

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

相关文章

MTGNN论文解读

模型架构 MTGNN 由多个模块组合而成&#xff0c;目标是捕捉多变量时间序列中的空间&#xff08;变量间&#xff09;和时间&#xff08;时序&#xff09;依赖。 图学习层&#xff1a;用于自适应地学习图的邻接矩阵&#xff0c;发现变量之间的关系。图卷积模块&#xff1a;根据邻…

C语言:函数栈帧的创建和销毁

目录 1.什么是函数栈帧2.理解函数栈帧能解决什么问题3.函数栈帧的创建和销毁的过程解析3.1 什么是栈3.2 认识相关寄存器和汇编指令3.3 解析函数栈帧的创建和销毁过程3.3.1 准备环境3.3.2 函数的调用堆栈3.3.3 转到反汇编3.3.4 函数栈帧的创建和销毁 1.什么是函数栈帧 在写C语言…

VSCode便捷开发

一、常用插件 Vue 3 Snippets、Vetur、Vue - Official 二、常用开发者工具 三、Vue中使用Element-UI 安装步骤&#xff1a; 1、在VSCode的终端执行如下指令&#xff1a; npm i element-ui -S 2、在main.js中全局引入&#xff1a; import Vue from vue; import ElementUI from …

二、tsp学习笔记——LINUX SDK编译

开发环境&#xff1a;window11 wsl ubuntu24.04 lypwslDESKTOP-39T8VTC:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04.1 LTS Release: 24.04 Codename: noble linux_sdk同步 tspi_linux_sdk_repo_202…

分析用户请求K8S里ingress-nginx提供的ingress流量路径

前言 本文是个人的小小见解&#xff0c;欢迎大佬指出我文章的问题&#xff0c;一起讨论进步~ 我个人的疑问点 进入的流量是如何自动判断进入iptables的四表&#xff1f;k8s nodeport模式的原理&#xff1f; 一 本机环境介绍 节点名节点IPK8S版本CNI插件Master192.168.44.1…

初阶数据结构:树---堆

目录 一、树的概念 二、树的构成 &#xff08;一&#xff09;、树的基本组成成分 &#xff08;二&#xff09;、树的实现方法 三、树的特殊结构------二叉树 &#xff08;一&#xff09;、二叉树的概念 &#xff08;二&#xff09;、二叉树的性质 &#xff08;三&#…

feign 远程调用详解

在平常的开发工作中&#xff0c;我们经常需要跟其他系统交互&#xff0c;比如调用用户系统的用户信息接口、调用支付系统的支付接口等。那么&#xff0c;我们应该通过什么方式进行系统之间的交互呢&#xff1f;今天&#xff0c;简单来总结下 feign 的用法。 1&#xff1a;引入依…

Sentinel的安装和做限流的使用

一、安装 Release v1.8.3 alibaba/Sentinel GitHubA powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件) - Release v1.8.3 alibaba/Sentinelhttps://github.com/alibaba/Senti…

讯飞智作 AI 配音技术浅析(三):自然语言处理

自然语言处理&#xff08;NLP&#xff09;是讯飞智作 AI 配音技术的重要组成部分&#xff0c;负责将输入的文本转换为机器可理解的格式&#xff0c;并提取出文本的语义和情感信息&#xff0c;以便生成自然、富有表现力的语音。 一、基本原理 讯飞智作 AI 配音的 NLP 技术主要包…

wxWidgets生成HTML文件,带图片转base64数据

编译环境大家可以看我之前的文章,CodeBlocks + msys2 + wx3.2,win10 这里功能就是生成HTML文件,没用HTML库,因为是自己固定的格式,图片是一个vector,可以动态改变数量的。 效果如下: #include <wx/string.h> #include <wx/file.h> #include <wx/ima…

【matlab基本使用笔记】

ctrl a i 代码格式化 fzero求非线性函数的根 arrayfun将函数应用于每个数组元素 format long长格式输出 format long g取消科学计数法 linspace logspace 一、界面使用 1.创建matlab脚本 利用.m后缀的脚本文件&#xff08;又称为m文件&#xff09;编程&#xff1a; 点击…

机器学习--python基础库之Matplotlib (1) 超级详细!!!

机器学习--python基础库Matplotlib 机器学习--python基础库Matplotlib0 介绍1 实现基础绘图-某城市温度变化图1.1绘制基本图像1.2实现一些其他功能 2 再一个坐标系中绘制多个图像3 多个坐标系显示-plt.subplots(面向对象的画图方法)4 折线图的应用场景 机器学习–python基础库M…

bat脚本实现自动化漏洞挖掘

bat脚本 BAT脚本是一种批处理文件&#xff0c;可以在Windows操作系统中自动执行一系列命令。它们可以简化许多日常任务&#xff0c;如文件操作、系统配置等。 bat脚本执行命令 echo off#下面写要执行的命令 httpx 自动存活探测 echo off httpx.exe -l url.txt -o 0.txt nuc…

一文解释nn、nn.Module与nn.functional的用法与区别

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;零基础入门PyTorch框架_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 …

Unity VideoPlayer播放视屏不清晰的一种情况

VideoPlayer的Rnder Texture可以设置Size,如果你的视屏是1920*1080那么就设置成1920*1080。 如果设置成其他分辨率比如800*600会导致视屏不清晰。

【玩转全栈】--创建一个自己的vue项目

目录 vue介绍 创建vue项目 vue页面介绍 element-plus组件库 启动项目 vue介绍 Vue.js 是一款轻量级、易于上手的前端 JavaScript 框架&#xff0c;旨在简化用户界面的开发。它采用了响应式数据绑定和组件化的设计理念&#xff0c;使得开发者可以通过声明式的方式轻松管理数据和…

揭秘区块链隐私黑科技:零知识证明如何改变未来

文章目录 1. 引言&#xff1a;什么是零知识证明&#xff1f;2. 零知识证明的核心概念与三大属性2.1 完备性&#xff08;Completeness&#xff09;2.2 可靠性&#xff08;Soundness&#xff09;2.3 零知识性&#xff08;Zero-Knowledge&#xff09; 3. 零知识证明的工作原理4. 零…

堆的实现——堆的应用(堆排序)

文章目录 1.堆的实现2.堆的应用--堆排序 大家在学堆的时候&#xff0c;需要有二叉树的基础知识&#xff0c;大家可以看我的二叉树文章&#xff1a;二叉树 1.堆的实现 如果有⼀个关键码的集合 K {k0 , k1 , k2 , …&#xff0c;kn−1 } &#xff0c;把它的所有元素按完全⼆叉树…

Ubuntu20.04 本地部署 DeepSeek-R1

一、下载ollama 打开 ollama链接&#xff0c;直接终端运行提供的命令即可。如获取的命令如下&#xff1a; curl -fsSL https://ollama.com/install.sh | sh确保是否安装成功可在终端输入如下命令&#xff1a; ollama -v注意&#xff1a; 如遇到Failed to connect to github.…

2025年2月6日笔记

第 12 届蓝桥杯 C 青少组中 / 高级组选拔赛&#xff08; STEMA &#xff09; 2020 年 11 月 22 日 真题第一题 解题思路&#xff1a; 第一&#xff1a;因为有整数集合的求和字样&#xff08;所以用for循环来做&#xff09; 第二&#xff1a;题中让我们累加1到N&#xff0c;所…