【Java设计模式】二、单例模式

news2024/10/2 6:26:25

文章目录

  • 1、懒汉式
  • 2、双重检查
  • 3、静态内部类
  • 4、饿汉式
  • 5、枚举
  • 6、单例模式的破坏:序列化和反序列化
  • 7、单例模式的破坏:反射

  • 单例模式即在程序中想要保持一个实例对象,让某个类只有一个实例
  • 单例类必须自己创建自己唯一的实例,并对外提供
  • 优点:减少了内存开销

单例模式的实现,有以下几种思路:

1、懒汉式

在需要使用对象的时候,才会创建。

public class LazySingleton  {
    private static LazySingleton lazySingleton = null;

    /**
     * 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象
     */
    private LazySingleton() {

    }

    /**
     * 单例对象的获取
     */
    public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

}

测试类启两个线程获取对象:

public class Test {
    public static void main(String[] args) {
        new Thread(() -> {
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(Thread.currentThread().getName() + "-->" + instance);
        }, "t1").start();
        new Thread(() -> {
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(Thread.currentThread().getName()+ "-->" + instance);
        }, "t2").start();
    }
}

发现可能出现获取到两个不同对象的情况,这是因为线程安全问题:

在这里插入图片描述

两个线程A、B,同时执行IF 这一行,被挂起,再被唤醒时继续往下执行,就会创建出两个不同的实例对象。那最先想到的应该是synchronized关键字解决,但这样性能底下,因为不管对象是否为null,每次都要等着获取锁。

//性能低下,一刀切,不可行
public static synchronized LazySingleton getInstance() {
     if (lazySingleton == null) {
         lazySingleton = new LazySingleton();
     }
     return lazySingleton;
 }

2、双重检查

通过两个IF判断,加上同步锁进行实现。

public class DoubleCheckSingleton {

    private static DoubleCheckSingleton doubleCheckSingleton;

    /**
     * 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象
     */
    private DoubleCheckSingleton(){

    }
    public static DoubleCheckSingleton getInstance(){
        if(doubleCheckSingleton == null){
            synchronized (DoubleCheckSingleton.class){
                if(doubleCheckSingleton == null){
                    doubleCheckSingleton = new DoubleCheckSingleton();
                }
            }
        }
        return doubleCheckSingleton;
    }
}

如此,再有A、B两个线程同时执行到第一个IF,只能有一个成功创建对象,另一个获取到锁后,第二重判断会告诉它已经有对象实例了。而亮点则在于,后面来获取对象的线程不用等着拿锁,第一个IF就能告诉它已有对象,不用再等锁了。

3、静态内部类

在单例类中,通过私有的静态内部类,创建单例对象。(加private修饰词的,出了本类无法调用和访问)

public class StaticInnerClassSingleton {

    /**
     * 私有的静态内部类实例化对象
     * 给内部类的属性赋值一个对象
     */
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    /**
     * 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象
     */
    private StaticInnerClassSingleton(){

    }

    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
}

外部类StaticInnerClassSingleton被加载时,其对象不一定被初始化,因为内部类没有被主动使用到。直到调用getInstance方法时,静态内部类InnerClass被加载,完成实例化。

静态内部类在被加载时,不会立即实例化,而是在第一次使用时才会被加载并初始化。

这种延迟加载的特性,使得我们可以通过静态内部类来实现在需要时创建单例对象。

4、饿汉式

  • 在调用时,就会创建单例对象
  • 通过静态代码块或者静态变量直接初始化
public class HungrySingleton {

    // 方式一:静态变量直接初始化
    // private static final HungrySingleton hungrySingleton = new HungrySingleton();
    private static HungrySingleton hungrySingleton = null;

    //  方式二:静态代码块
    static {
        hungrySingleton = new HungrySingleton();
    }

    private HungrySingleton(){

    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}


5、枚举

  • 单例模式的最佳实现方式
  • 可有效防止对单例模式的破坏
public enum EnumSingleton {
    INSTANCE;

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

6、单例模式的破坏:序列化和反序列化

通过流将单例对象,序列化到文件中,然后再反序列化读取出来。发现通过反序列化方式创建出来的对象内存地址,和原对象不一样,单例模式被破坏。

public class TestSerializer {

    public static void main(String[] args) throws Exception {
        //懒汉式
        LazySingleton instance = LazySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));
        oos.writeObject(instance);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton"));
        LazySingleton objInstance = (LazySingleton) ois.readObject();
        System.out.println(instance);
        System.out.println(objInstance);
        System.out.println(instance == objInstance);
    }
}

在这里插入图片描述

可以发现单例模式的五种实现方式中,只有枚举不会被破坏单例模式。如果非要用其他几种模式,可以加readResolve方法来重写反序列化逻辑。因为反序列化创建对象时,是通过反射创建的,反射会调用readResolve方法。没有重写readResolve方法时,会通过反射创建一个新的对象,从而破坏了单例模式。

public class LazySingleton implements Serializable {
    private static LazySingleton lazySingleton = null;

    /**
     * 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象
     */
    private LazySingleton() {
    }

    /**
     * 单例对象的获取
     */
    public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

    private Object readResolve(){
        return lazySingleton;
    }


}

在这里插入图片描述

也可考虑使用@JsonCreator注解。

7、单例模式的破坏:反射

  • 通过字节码对象,创建构造器对象
  • 通过构造器对象,初始化单例对象
  • 由于单例对象的构造方法是private私有的,调用构造器中的方法,赋予权限,创建单例对象

注意私有修饰词时,反射会IllegalAccessException

在这里插入图片描述

处理下private问题,用懒汉模式验证:

public class TestReflect {
    public static void main(String[] args) throws Exception{
        //创建字节码对象
        Class<LazySingleton> clazz = LazySingleton.class;
        //构造器对象
        Constructor<LazySingleton> constructor = clazz.getDeclaredConstructor();
        //赋予权限
        constructor.setAccessible(true);
        //解决了私有化问题,获取实例对象
        LazySingleton instanceReflect = constructor.newInstance();
        //直接获取单例对象
        LazySingleton instanceSingle = LazySingleton.getInstance();
        System.out.println(instanceReflect);
        System.out.println(instanceSingle);
        System.out.println(instanceReflect == instanceSingle);

    }
}

在这里插入图片描述

用枚举的方式验证:

public class TestEnumReflect {
    public static void main(String[] args) throws Exception {
        Class<EnumSingleton> clazz = EnumSingleton.class;
        //枚举下的单例模式,创建构造方法时,需要给两个参数,薮泽NoSuchMethodException
        //这两个参数是源码中的体现,一个是String,一个是int
        Constructor<EnumSingleton> constructor = clazz.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        EnumSingleton instanceReflect = constructor.newInstance("test",1234);
        EnumSingleton instanceSingleton = EnumSingleton.getInstance();
        System.out.println(instanceReflect);
        System.out.println(instanceSingleton);
        System.out.println(instanceReflect == instanceSingleton);

    }
}

运行报错:Cannot reflectively create enum objects,即反射创建枚举的单例对象,是不允许的:

在这里插入图片描述

在其他单例模式的实现方式里,也可以实现不允许通过反射创建对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1472087.html

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

相关文章

mysql的增删改查(常用)

增(insert) 语法&#xff1a; insert into 表名&#xff08;字段&#xff09; values( 字段对应的值) 案例&#xff1a; 创建一个学生表 结构如下&#xff1a; create table student(id int ,name varchar(20),age int); 向表中插入2条数据 create table student(id int ,n…

802.11局域网的 MAC 层协议、CSMA/CA

目录 802.11 局域网的 MAC 层协议 1 CSMA/CA 协议 无线局域网不能使用 CSMA/CD 无线局域网可以使用 CSMA 协议 802.11 的 MAC 层 分布协调功能 DCF 点协调功能 PCF CSMA/CA 协议的要点 2 时间间隔 DIFS 的重要性 SIFS DIFS 3 争用信道的过程 时隙长度的确定 退避…

Java+SpringBoot+Vue+MySQL构建银行客户管理新平台

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

HUAWEI Programming Contest 2024(AtCoder Beginner Contest 342)

D - Square Pair 题目大意 给一长为的数组&#xff0c;问有多少对&#xff0c;两者相乘为非负整数完全平方数 解题思路 一个数除以其能整除的最大的完全平方数&#xff0c;看前面有多少个与其余数相同的数&#xff0c;两者乘积满足条件&#xff08;已经是完全平方数的部分无…

2-22 方法、面向对象、类、JVM内存、构造方法

文章目录 方法的重载面向对象类、属性和方法成员变量默认值属性JVM简单内存分析栈空间堆空间 构造方法执行过程构造器注意点 方法的重载 一个类中名称相同&#xff0c;但是参数列表不同的方法 参数列表不同是指&#xff1a; 形参类型形参个数形参顺序 面向对象 field —— …

JAVA工程师面试专题-《JVM篇》

目录 一、运行时数据区 1、说一下JVM的主要组成部分及其作用&#xff1f; 2、说一下 JVM 运行时数据区 &#xff1f; 3、说一下堆栈的区别 4、成员变量、局部变量、类变量分别存储在什么地方&#xff1f; 5、类常量池、运行时常量池、字符串常量池有什么区别&#xff1f;…

Amazon Generative AI | 基于 Amazon 扩散模型原理的代码实践之采样篇

以前通过论文介绍 Amazon 生成式 AI 和大语言模型&#xff08;LLMs&#xff09;的主要原理之外&#xff0c;在代码实践环节主要还是局限于是引入预训练模型、在预训练模型基础上做微调、使用 API 等等。很多开发人员觉得还不过瘾&#xff0c;希望内容可以更加深入。因此&#x…

软件设计师软考题目解析05 --每日五题

想说的话&#xff1a;要准备软考了。0.0&#xff0c;其实我是不想考的&#xff0c;但是吧&#xff0c;由于本人已经学完所有知识了&#xff0c;只是被学校的课程给锁在那里了&#xff0c;不然早找工作去了。寻思着反正也无聊&#xff0c;就考个证玩玩。 本人github地址&#xf…

132.乐理基础-快速识别音程(二)

上一个内容&#xff1a;131.乐理基础-快速识别音程&#xff08;一&#xff09;-CSDN博客 上一个内容里练习的答案&#xff1a; 无论哪两个音&#xff0c;也就是无论升降记号多么离谱&#xff0c;该怎样去判断它是什么音程&#xff0c;首先就要记住&#xff08;现在只需要只需要…

es6 中的生成器 generator / 迭代器 / async /await 到底是个啥,使用场景

生成器 generator 到底是个啥 是一个函数 可以用来遍历数据结构是解决异步编程的一种方案进行数据流的生成和控制协程和状态机返回一个生成器对象/可迭代对象 生成器对象&#xff1a; 生成器对象是由生成器函数返回的对象&#xff0c;它符合迭代器协议&#xff08;Iterator Pr…

车规级MCU的行业走向

1 主要厂家 车规级MCU&#xff08;车用微控制器单元&#xff09;的主要厂家包括&#xff1a; NXP半导体&#xff1a;NXP是全球领先的车规级MCU提供商之一&#xff0c;提供广泛的产品用于汽车控制和管理系统。英飞凌科技&#xff1a;作为汽车半导体的领导者之一&#xff0c;英飞…

现代化数据架构升级:毫末智行自动驾驶如何应对年增20PB的数据规模挑战?

毫末智行是一家致力于自动驾驶的人工智能技术公司&#xff0c;其前身是长城汽车智能驾驶前瞻分部&#xff0c;以零事故、零拥堵、自由出行和高效物流为目标&#xff0c;助力合作伙伴重塑和全面升级整个社会的出行及物流方式。 在自动驾驶领域中&#xff0c;是什么原因让毫末智行…

Spring Session:Redis序列化配置|Session事件监听

Spring Session是可配置的。 Redis Configuration JSON序列化配置 Spring Session默认使用Java对象序列化机制对Session属性值进行序列化。 预定义类SysUser 先来定义一个SysUser类&#xff0c;在下面的演示案例中会用到。 package com.example.demo.model.entity;import j…

Linux环境下的性能分析 之 CPU篇(二)

2、CPU的使用情况分析 a、类似任务管理器的top & htop 说到对CPU的性能分析&#xff0c;大家一定不会忘记windows下那个最熟悉的工具&#xff1a;任务管理器。 有了这个玩意儿&#xff0c;我们就可以看到CPU的利用率&#xff0c;以及每一个进程所占用的CPU资源。那在Linu…

C++类和对象篇

1.类的定义 在C语言结构体中&#xff0c;只能定义变量&#xff0c;C扩展了类的概念&#xff0c;能够在类定义函数&#xff1b;同时&#xff0c;struct仍然可以使用&#xff0c;但更常用class来表示类 1.1类中函数的两种定义方式 函数的声明和定义都在类中 class Date { public:…

【前端素材】推荐优质后台管理系统Space平台模板(附源码)

一、需求分析 综上所述&#xff0c;后台管理系统在多个层次上提供了丰富的功能和细致的管理手段&#xff0c;帮助管理员轻松管理和控制系统的各个方面。其灵活性和可扩展性使得后台管理系统成为各种网站、应用程序和系统不可或缺的管理工具。 当我们从多个层次来详细分析后台…

【c语言】字符函数和字符串函数(上)

前言 在编程的过程中&#xff0c;我们经常要处理字符和字符串&#xff0c;为了⽅便操作字符和字符串&#xff0c;C语⾔标准库中提供了⼀系列库函数~ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 前言 1. 字符分…

代码随想录刷题第43天

第一题是最后一块石头的重量IIhttps://leetcode.cn/problems/last-stone-weight-ii/&#xff0c;没啥思路&#xff0c;直接上题解了。本题可以看作将一堆石头尽可能分成两份重量相似的石头&#xff0c;于是问题转化为如何合理取石头&#xff0c;使其装满容量为石头总重量一半的…

DataSpell 2023:专注于数据,加速您的数据科学之旅 mac/win版

JetBrains DataSpell 2023是一款专为数据科学家和数据分析师设计的集成开发环境&#xff08;IDE&#xff09;。这款IDE提供了强大的数据分析和可视化工具&#xff0c;旨在帮助用户更快速、更高效地进行数据科学工作。 DataSpell 2023软件获取 DataSpell 2023在保持其一贯的数…

【零代码研发】OpenCV实验大师工作流引擎C++ SDK演示

学习《OpenCV应用开发&#xff1a;入门、进阶与工程化实践》一书 做真正的OpenCV开发者&#xff0c;从入门到入职&#xff0c;一步到位&#xff01; OpenCV开发痛点 传统图像算法开发最好的开源解决方案是OpenCV视觉库&#xff0c;但是OpenCV中收录了2000的传统算法&#xf…