详解Java中简单的设计模式——单例模式的演进和推荐写法

Java单例模式的七种写法:从饿汉式到枚举式的演进 本文详细介绍了Java中实现单例模式的七种方法,包括饿汉式、懒汉式、双重检查锁、静态内部类、枚举式等。每种实现方式都分析了其优缺点和适用场景,其中枚举式单例被《Effective Java》推荐为最佳实践,能有效防止反射和序列化攻击。文章还强调了单例模式的核心要素,如线程安全、延迟加载和防止破坏单例的措施。通过对比不同实现,开发者可以根据具体需求选择最合适的单例模式实现方案。 阅读详情

点关注,不迷路;持续更新Java架构相关技术及资讯热文!!!

眼尖的朋友可能还看见了此博客页面左上角还有惊喜哟

前言

如下是之前总结的 C++ 版的;软件开发常用设计模式—单例模式总结(c++版),对比发现 Java 实现的单例模式和 C++ 的在线程安全上还是有些区别的。

概念不多说,没意思,我自己总结就是:

有这样一个类,该类在生命周期内有且只能有一个实例,该类必须自己创建自己的这个唯一实例,该类必须给所有其他对象提供这一实例(提供全局访问点),这样的类就叫单例类。

简单的说就是满足三个条件:

1、生命周期内有且只能有一个实例

2、自己提供这个独一无二的实例

3、该实例必须是能全局访问的

需要的考虑的细节

进一步,单例类,最好能实现懒加载,随用随生成,而不是初始化的时候就生成,提高启动速度和优化内存。

还有应该考虑并发环境下的场景,多线程的单例模式实现有什么难点,回答这个问题,必须先知道Java的内存模型

考虑黑客会做反序列化的攻击

考虑黑客会做反射的攻击,因为反射可以访问私有方法

单线程环境下懒加载的单例

如果程序确认没有多线程的使用场景,完全可以简单一些写。

public class NoThreadSafeLazySingleton {
    private static NoThreadSafeLazySingleton lazySingleton = null;

    private NoThreadSafeLazySingleton() {
    }

    public static NoThreadSafeLazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            lazySingleton = new NoThreadSafeLazySingleton();
        }

        return lazySingleton;
    }
}

很简单,但是只适用于单线程环境

线程安全的懒加载单例

原理也很简单,没什么可说的,如下示例代码:

public class ThreadSafeLazySingleton {
    private static volatile ThreadSafeLazySingleton lazySingleton = null;

    private ThreadSafeLazySingleton() {
    }

    public static ThreadSafeLazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            synchronized (ThreadSafeLazySingleton.class) {
                if (lazySingleton == null) {
                    lazySingleton = new ThreadSafeLazySingleton();
                }
            }
        }

        return lazySingleton;
    }
}

主要是注意 volatile 关键字的使用,否则这种所谓双重检查的线程安全的单例是有 bug 的。

静态内部类方案

在某些情况中,JVM 隐含了同步操作,这些情况下就不用自己再来进行同步控制了。这些情况包括:

  • 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
  • 访问final字段时
  • 在创建线程之前创建对象时
  • 线程可以看见它将要处理的对象时

在静态内部类里去创建本类(外部类)的对象,这样只要不使用这个静态内部类,那就不创建对象实例,从而同时实现延迟加载和线程安全。

public class Person {
    private String name;
    private Integer age;

    private Person() {
    }

    private Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    // 在静态内部类里去创建本类(外部类)的对象
    public static Person getInstance() {
        return Holder.instatnce;
    }

    // 静态内部类相当于外部类 Person 的 static 域,它的对象与外部类对象间不存在依赖关系,因此可直接创建。
    // 因为静态内部类相当于其外部类 Person 的静态成员,所以在第一次被使用的时候才被会装载,且只装载一次。
    private static class Holder {
        // 内部类的对象实例 instatnce ,是绑定在外部 Person 对象实例中的
        // 静态内部类中可以定义静态方法,在静态方法中只能够引用外部类中的静态成员方法或者成员变量,比如 new Person
        // 使用静态初始化器来实现线程安全的单例类,它由 JVM 来保证线程安全性。
        private static final Person instatnce = new Person("John", 31);
    }
}

静态内部类相当于外部类 Person 的 static 域(静态成员),它的对象与外部类对象间不存在依赖关系,因此可直接创建。

既然,静态内部类相当于其外部类 Person 的静态成员,所以在第一次被使用的时候才被会装载,且只装载一次,实现了懒加载和单例。

而且,使用静态初始化器来实现单例类,是线程安全的,因为由 JVM 来保证线程安全性

客户端调用

    Person person = Person.getInstance();

该方案实现了,线程安全的单例 + 懒加载的单例,但是并不能防反序列化攻击,需要额外的加以约束。

反序列化攻击单例类

其实这个 case 没必要说太多,知道就行,因为哪里就这么巧,一个能序列化的类(实现了Serializable/Externalizable接口的类),就恰恰是单例的呢?

看下面例子,把 Person 类改造为能序列化的类,然后用反序列攻击单例

public class SerializationTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person person = Person.getInstance();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("person"));
        objectOutputStream.writeObject(person);

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("person"));
        Person person1 = (Person) objectInputStream.readObject();

        System.out.println(person == person1); // false
    }
}

比较两个 person 实例地址,是 false,说明生成了两个对象,违背了单例类的初衷,那么为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象

    public Object readResolve() {
        return Holder.instatnce;
    }

原理是当从 I/O 流中读取对象时,ObjectInputStream 类里有 readResolve() 方法,该方法会被自动调用,期间经过种种逻辑,最后会调用到可序列化类里的 readResolve()方法,这样可以用 readResolve() 中返回的单例对象直接替换在反序列化过程中创建的对象,实现单例特性。

也就是说,无论如何,反序列化都会额外创建对象,只不过使用 readResolve() 方法可以替换之。

反射攻击单例类

直接看例子,做法很简单,通过 Java 的反射机制,看看能不能拿到单例类的私有构造器,并且改变构造器的访问属性

public class ReflectTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
        Person person = Person.getInstance();

        Class clazz = Class.forName("com.dashuai.D13Singleton.Person");
        Constructor constructor = clazz.getDeclaredConstructor();
//        constructor.setAccessible(true);
        Person person1 = (Person) constructor.newInstance();

        System.out.println(person == person1); // false
    }
}

运行抛出了异常:

但是,如果把注释的行打开,就不会出错,且打印 false。

网上有一些解决方案,比如在构造器里加判断,如果二次调用就抛出异常,其实也没从根本上解决问题。

解决所有问题的方案——枚举实现单例类

目前公认的最佳方案,代码极少,线程安全,防止反射和序列化攻击

public enum EnumSingleton {
    ENUM_SINGLETON;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
////////////////////////调用
EnumSingleton.ENUM_SINGLETON.setName("dashuai");
System.out.println(EnumSingleton.ENUM_SINGLETON.getName());

所有的变量都是单例的。至于为什么,可以通过反编译工具查看枚举的源码。可以安装 idea 的 jad 插件,会发现就是按照单例模式设计的。

享元模式和单例模式的异同

享元模式是对象级别的, 也就是说在多个使用到这个对象的地方都只需要使用这一个对象即可满足要求。

单例模式是类级别的, 就是说这个类必须只能实例化出来一个对象。

可以这么说, 单例是享元的一种特例, 设计模式不用拘泥于具体代码, 代码实现可能有n多种方式, 而单例可以看做是享元的实现方式中的一种, 他比享元模式更加严格的控制了对象的唯一性

使用单例的场景和条件是什么?

1、单例类只能有一个实例。

2、单例类必须自己创建自己的唯一实例。

3、单例类必须给所有其他对象提供这一实例。


写在最后

眼尖的朋友可能还看见了此博客页面左上角还有惊喜哟

点关注,不迷路;持续更新Java架构相关技术及资讯热文!!!

Java单例模式终极指南:5种实现方式详解与防坑实战 在云原生和微服务架构下,传统的单例模式正在发生革命性变化。某电商平台通过改造单例模式,实现配置中心的动态刷新机制,使系统在不重启的情况下完成配置更新,QPS提升40%。Kubernetes环境下的单例模式:使用Lease锁实现跨Pod单例Serverless架构适配:无状态函数中的单例管理响应式编程整合:结合RxJava实现异步单例学习资源推荐《Effective Java》第3章 Item 3Java并发编程实战 第16章Spring Framework官方文档Bean作用域章节。 阅读详情

相关推荐

2024Java开发必读书籍推荐

作为一名 Java 工程师,持续学习和提升是非常重要的。阅读优秀的技术书籍可以帮助我们深入理解 Java 编程语言的核心概念和最佳实践。在本文中,我将向大家推荐一共 10 本 Java 工程师必读的经典书籍,旨在帮助大家扩展知识领域,提高编程技能和解决问题的能力。

Wayn111的博客 1312

几种简单的设计模式

三、简单设计模式 1.装饰模式(包装模式) 装饰模式就是对某个类进行包装,目的是改写已存在的类的某个方法或某些方法,那么最有一个例子是改写jdbc链接池链接connection的关闭方法。在以前开发有手写链接池的,连接池获取连接,链接用完后应该放回到池中而不是关闭掉,如果按照原始的DriverManager.getConnection()得到的connection是MySQL驱动实现好的子类,因为connection本身是接口,只提供了标准方法,具体实现都是子类,那么close方法就是直接关闭掉,但我.

xiaobai1024的博客 906

csp-j/s

CSP-J/S 是中国计算机学会(CCF)主办的 ‌‌(Certified Software Professional Junior/Senior),是信息学奥赛(NOI)体系的入门级选拔考试,也是国内最具权威性的青少年编程能力认证之一。

gxyxxrYYDS的博客 2028

简单易懂的设计模式

总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 一, 创建型模式 1.简单工厂 简单工厂模式就是写一个类接收参数并且,根据参数创建对象 简单工厂存在的问题与解决方法: 简单工厂模式有一个问题就是

ywdevil1314的博客 570

【图文步骤】EC-Engineer部分操作详细步骤

EC-Engineer部分功能详细使用手册

weixin_42212468的博客 6171

机器人C++开源库The Robotics Library (RL)使用手册(三)

进入VS工程,我们先看看这些功能函数及其依赖库的分布关系:rl命名空间下,主要有八大模块。搞定VS后将逐个拆解。

qq_34839823的博客 1736

23种设计模式攻关

这个静态方法会检查实例是否已经存在,如果存在则返回现有实例,如果不存在则创建一个新实例并返回。

小白先生的博客 1168

Abel逆变换及其求解方法

Abel逆变换常应用在轴对称的、光学薄的火焰、羽流或等离子体重建。

weixin_49783507的博客 8371

25000 字详解 23 种设计模式,原来可以这么简单!

创建型模式总体上比较简单,它们的作用就是为了产生实例对象,算是各种工作的第一步了,因为我们写的是面向对象的代码,所以我们第一步当然是需要创建一个对象了。简单工厂模式最简单;工厂模式在简单工厂模式的基础上增加了选择工厂的维度,需要第一步选择合适的工厂;抽象工厂模式有产品族的概念,如果各个产品是存在兼容性问题的,就要用抽象工厂模式。单例模式就不说了,为了保证全局使用的是同一对象,一方面是安全性考虑,一方面是为了节省资源;建造者模式专门对付属性很多的那种类,为了让代码更优美;

2401_84048034的博客 1053

ubuntu18.04安装xenomai3.1(百分百成功版)

ubuntu18.04安装xenomai3.1(百分百成功版),保姆级教学!!!

h542812556sdu的博客 2065

数学建模学习(78):多输入多输出回归预测模型(结合XGBoost实现)

多输入多输出回归预测问题求解

全栈川川 8130

几种简单的设计模式:单例模式、代理模式、观察者模式、工厂模式

常见的设计模式

m0_51553630的博客 2008

Wazuh部署记录

Wazuh是一个免费的开源安全平台,统一了XDR和SIEM功能。它可以保护内部部署、虚拟化、容器化和基于云的环境中的工作负载。Wazuh帮助组织和个人保护其数据资产免受安全威胁。它被全球数千个组织广泛使用,从小型企业到大型企业。443:仪表盘界面访问端口1515:探针连接服务器端口1514:探针回传信息端口以上3个端口必须打开防火墙。

墨痕诉清风的博客 1561

本人秃顶程序员最简单的设计模式——单例模式的演进和推荐写法(Java版)

【本人秃顶程序员】最简单的设计模式——单例模式的演进和推荐写法(Java 版)https://blog.youkuaiyun.com/weixin_44175121/article/details/86762844←←←←←←←←←←←← 快!点关注 前言 如下是之前总结的 C++ 版的;软件开发常用设计模式—单例模式总结(c++版),对比发现 Java 实现的单例模式和 C++ 的在线程安全上还是有些区别的...

rede 1531

从4A架构的角度出来 帮我分析一下:数字化转型深水区:企业架构如何成为价值重构的核心引擎?

4A 架构的本质是构建企业的数字神经系统,通过业务感知、数据传输、决策执行、技术支撑的闭环,实现从效率提升到生态重构的质变。正如华为通过该架构实现全球研发资源统一调度,某零售集团借此打造线上线下融合的新零售体系,当业务、数据、应用、技术四个维度形成共振时,企业将获得持续进化的数字免疫力,在不确定的市场环境中保持韧性生长。这一过程不仅需要技术工具的升级,更依赖组织文化的变革 —— 从 “部门墙” 到 “能力网” 的转型,才是数字化转型深水区的终极挑战。

金牌架构师 1046

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

【本人秃顶程序员】最简单的设计模式——单例模式的演进和推荐写法(Java 版)

←←←←←←←←←←←← 快!点关注 前言 如下是之前总结的 C++ 版的;软件开发常用设计模式—单例模式总结(c++版),对比发现 Java 实现的单例模式和 C++ 的在线程安全上还是有些区别的。 概念不多说,没意思,我自己总结就是: 有这样一个类,该类在生命周期内有且只能有一个实例,该类必须自己创建自己的这个唯一实例,该类必须给所有其他对象提供这一实例(提供全局访问点),这样的类就叫单例类。...

本人秃顶程序员的博客 3285

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

java单例模式_最简单的设计模式—单例模式的演进和推荐写法(Java 版)

前言如下是之前总结的 C++ 版的;软件开发常用设计模式—单例模式总结(c++版),对比发现 Java 实现的单例模式和 C++ 的在线程安全上还是有些区别的。概念不多说,没意思,我自己总结就是:有这样一个类,该类在生命周期内有且只能有一个实例,该类必须自己创建自己的这个唯一实例,该类必须给所有其他对象提供这一实例(提供全局访问点),这样的类就叫单例类。简单的说就是满足三个条件:1、生命周期内有且...

weixin_39835607的博客 95

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

最简单的设计模式—单例模式的演进和推荐写法(Java 版)

     前言   如下是之前总结的 C++ 版的;软件开发常用设计模式—单例模式总结(c++版),对比发现 Java 实现的单例模式和 C++ 的在线程安全上还是有些区别的。   概念不多说,没意思,我自己总结就是:   有这样一个类,该类在生命周期内有且只能有一个实例,该类必须自己创建自己的这个唯一实例,该类必须给所有其他对象提供这一实例(提供全局访问点),这样的类就叫单例类。   简单的说就是满足三个条件:   1、生命周期内有且只能有一个实例   2、自己提供这个独一无二的实例..

iqifenxia的博客 174

最简单的设计模式——单例模式的演进和推荐写法(Java 版)

前言 如下是之前总结的 C++ 版的;软件开发常用设计模式—单例模式总结(c++版),对比发现 Java 实现的单例模式和 C++ 的在线程安全上还是有些区别的。 概念不多说,没意思,我自己总结就是: 有这样一个类,该类在生命周期内有且只能有一个实例,该类必须自己创建自己的这个唯一实例,该类必须给所有其他对象提供这一实例(提供全局访问点),这样的类就叫单例类。 简单的说就是满足三...

diaoyanqin5061的博客 180

【本人秃顶程序员】最简单的设计模式——单例模式的演进和推荐写法(Java 版)...

←←←←←←←←←←←← 快!点关注 前言 如下是之前总结的 C++ 版的;软件开发常用设计模式—单例模式总结(c++版),对比发现 Java 实现的单例模式和 C++ 的在线程安全上还是有些区别的。 概念不多说,没意思,我自己总结就是: 有这样一个类,该类在生命周期内有且只能有一个实例,该类必须自己创建自己的这个唯一实例,该类必须给所有其他对象提供这一实例(提供全局访问点),这样的类就叫单例类。...

weixin_34343308的博客 244

单例模式详细总结(优缺点、可用方案、演进与原理)-设计模式

饿汉式单例模式、懒汉式单例模式、枚举单例模式的优缺点,单例模式多版本演进与原理。涉及线程安全(多线程)、反序列化、反射攻击等

phs999的博客 549

单例模式详解:实现方式、演进与最佳实践

单例模式是设计模式中最基础且常用的创建型模式,其核心目标是确保一个类仅有一个实例,并提供全局访问点。从原理上看,它通过控制类的实例化过程来管理资源共享,特别适用于需要集中管理的场景如配置服务、数据库连接池等基础设施组件。在Java等面向对象语言中,单例的实现经历了从基础饿汉式/懒汉式到双重检查锁定、静态内部类乃至枚举单例的演进,每种方式在线程安全性与性能间各有取舍。现代开发中,借助Spring框架的依赖注入或Kotlin的object语法能更优雅地实现单例。值得注意的是,虽然单例模式能解决资源冲突问题,但需

weixin_30843121的博客 330

设计模式之单例模式笔记

什么是单例设计模式呢? 定义:单例模式是指在内存中只会创建且仅创建一次对象的设计模式。 在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。 一、单例的分类 饿汉式单例 在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。 懒汉式单例 在需要用到对象时才实例化对象,此时需要注意并发访问的问题。 二、单例模式的演进 1. 饿汉式单例 2. 懒汉式单例版本1

欲望以提升热忱 毅力以磨平高山 364

每天认识一个设计模式-单例模式:全局唯一的对象管家

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。作为创建型模式中最具争议也最基础的设计模式,一直以来都备受开发者的关注。它通过严格的对象实例控制机制,确保全局唯一性与高效访问,为我们的程序提供了一种简洁而强大的解决方案。无论是在资源管理、配置中心,还是在各种系统级组件中,单例模式都发挥着不可或缺的作用。​本文将深入剖析单例模式的实现演进与现代化应用实践,一起来学习一下吧~

Young_深情不及里子的博客 1275

Java编程中的设计模式从单例模式到策略模式的实战解析

}```### 工厂模式的场景应用。

lixiangwei1989的博客 300
上一篇: 金三银四京东面试真题,被问到这些,我也是醉了
下一篇: Java程序员注意,字节跳动研发面试官有话说!
程序员摩西
博客等级 码龄7年 45粉丝 104原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值