Java设计模式之单例模式(多种实现方式)

news2024/9/23 5:34:51

虽然写了很多年代码,但是说真的对设计模式不是很熟练,虽然平时也会用到一些,但是都没有深入研究过,所以趁现在有空练下手

这章主要讲单例模式,也是最简单的一种模式,但是因为spring中bean的广泛应用,所以现在单例模式在实际应用中其实很少见

在Spring中,默认情况下,Bean 是单例的,这意味着 Spring 容器会在第一次请求该 Bean 时创建一个实例,并且在整个应用程序的生命周期中保持该实例的单一性。换句话说,每次从 Spring 容器中请求相同的 Bean 时,都会得到相同的实例。

单例模式是一种常见的设计模式,适用于以下情况:

1.资源共享:当系统中需要共享某个资源(如数据库连接池、线程池、配置信息等)的时候,可以使用单例模式确保全局只有一个实例,避免资源的重复创建和浪费。

2.对象缓存:在需要频繁创建和销毁对象的情况下,可以使用单例模式将对象缓存起来,提高性能。

3.线程池:线程池通常被设计为单例,以确保在整个应用程序中只有一个线程池实例,用于管理线程的生命周期和执行任务。

4.日志对象:在系统中使用单例模式创建日志对象,可以确保所有的日志信息被统一记录,避免出现混乱的日志信息。

5.配置文件对象:将系统中的配置信息封装到单例对象中,可以方便地进行读取和修改。

6.对话框、窗口等界面组件:在图形用户界面(GUI)程序中,通常只需要一个对话框或窗口实例,可以使用单例模式确保全局只有一个实例。

7.管理器类:例如线程管理器、事件管理器等,这些管理器类通常被设计为单例,以便在整个系统中统一管理资源和事件。

总之,任何需要在系统中全局唯一存在的对象,且需要被频繁访问和共享的情况下,都可以考虑使用单例模式。

首先是最简单实用的饿汉模式

优点:

1.线程安全: 饿汉模式在类加载时就创建实例,并且实例是静态的 final 变量,因此在多线程环境下是线程安全的,不需要额外的线程同步控制。

2.简单易用: 饿汉模式的实现非常简单,通过静态变量初始化的方式就可以保证实例的唯一性和全局可访问性,不需要复杂的代码结构。

3.无需考虑懒加载和线程安全问题: 由于实例是在类加载时就创建好的,所以不需要考虑懒加载和线程安全问题,避免了相关的复杂性。

4.性能较好: 因为实例是在类加载时就创建好的,所以在获取实例时无需进行额外的判断和同步操作,性能较好。

缺点:

1.资源浪费: 饿汉模式在应用程序启动时就创建实例,并且实例是在整个应用程序生命周期内存在的,可能会导致资源的浪费。特别是如果实例占用大量资源或者需要较长时间进行初始化,可能会影响应用程序的启动速度。

2.不支持延迟加载: 饿汉模式不支持延迟加载,因为实例是在类加载时就创建好的,无法根据需要进行延迟加载。

3.可能导致类加载较慢: 如果一个类的实例创建比较耗时,那么在类加载时就会导致类加载较慢,影响整个应用程序的启动速度。

综上所述,饿汉模式适用于对性能要求较高,且实例创建比较简单且资源消耗较小的情况下。但是需要注意可能存在的资源浪费问题,特别是对于大型对象或者需要耗时初始化的实例。

直接上代码(建议使用)

public class EagerSingleton {

    // 在类加载时就创建实例,并初始化为静态变量
    private static final EagerSingleton instance = new EagerSingleton();

    // 私有化构造方法,防止外部实例化
    private EagerSingleton() {}

    // 获取单例实例的方法
    public static EagerSingleton getInstance() {
        try {
        	// 模拟处理业务逻辑耗时
            Thread.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i= 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(EagerSingleton.getInstance().hashCode());
            }).start();
        }
    }
}

执行一下
可以看到hashCode都是一样的,这里有人可能会说hashCode一样并不代表对象一样,我只能说你的确是对的,但这不在本章讲解范围内

在这里插入图片描述

然后我们再来看一下懒汉模式

优点:

1.延迟加载(Lazy Loading): 懒汉模式在首次访问时才会创建实例,避免了在程序启动时就创建对象实例,节省了内存和系统资源。

2.节省资源: 因为实例是在需要时才创建的,所以在大部分情况下不会占用额外的资源。

3.线程安全问题相对简单: 在单线程环境下,懒汉模式不需要额外的线程同步机制来保证线程安全,实现简单。

缺点:

1.线程安全性问题: 在多线程环境下,懒汉模式可能存在线程安全问题。当多个线程同时调用 getInstance() 方法时,如果没有进行额外的线程同步处理,可能会导致创建多个实例。

2.性能问题: 在并发环境下,由于需要额外的线程同步控制,懒汉模式的性能可能会受到一定影响。例如,使用双重检查锁(Double-Checked Locking)来确保线程安全性,会增加额外的开销。

3.可能存在反序列化问题: 当类实现了 Serializable 接口,并且对象被序列化然后再反序列化时,如果没有正确地处理单例对象,可能会破坏单例的约束,导致出现多个实例。

4.不适用于高并发场景: 在高并发场景下,频繁调用 getInstance() 方法可能会导致性能瓶颈,因为所有线程都需要竞争同一个锁来获取实例。

综上所述,懒汉模式适用于单线程环境或者对性能要求不是非常高的场景,但在多线程环境下需要特别注意线程安全性问题,并且需要针对性能做出权衡。

直接上代码(不建议使用)

public class EagerSingleton {

    // 在类加载时就创建实例,并初始化为静态变量
    private static final EagerSingleton instance = new EagerSingleton();

    // 私有化构造方法,防止外部实例化
    private EagerSingleton() {}

    // 获取单例实例的方法
    public static EagerSingleton getInstance() {
        try {
            // 模拟处理业务逻辑耗时
            Thread.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i= 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(EagerSingleton.getInstance().hashCode());
            }).start();
        }
    }
}

执行一下
发现没有,hashCode都不一样,虽然hashCode相同不能证明对象是同一个,但是hashCode不相同肯定不是同一个对象,这说明其实是线程不安全的,因此这种写法其实是被淘汰了的

在这里插入图片描述

上面的那种写法虽然不推荐使用,但是提供了一种思路,就是只在需要的时候才加载,其主要目的还是为了节省资源(现在的硬件其实都很强大,这点资源省不省问题其实不大,这也让我想起了很多年前我刚入行还在写C++,当时问我师父说这个指针要是忘了释放怎么办,他跟我说没关系的,现在电脑都很牛逼,这点资源浪费根本影响不了什么)

好了下面我们来完善一下懒汉模式,最简单的方法就是使用synchronized关键字来保证线程的安全,当然同时也就伴随着性能的损耗(不推荐使用)
这里直接用synchronized关键字锁整个方法

public class LazySingleton {

    // 注意:volatile关键字是必须的,防止指令重排序
    private static volatile LazySingleton instance;

    // 私有化构造方法,防止外部实例化
    private LazySingleton() {}

    // 获取单例实例的方法
    public static synchronized LazySingleton getInstance() {
        // 在第一次调用时才创建实例
        if (instance == null) {
            try {
                // 模拟处理业务逻辑耗时
                Thread.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            instance = new LazySingleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i= 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(LazySingleton.getInstance().hashCode());
            }).start();
        }
    }
}

执行一下

在这里插入图片描述

上面那个示例虽然保证了一个实例,但是性能上还是不如意,如是再优化一下就出现了下面这种(不是很推荐,因为看起来很复杂)
这里只在需要的地方加synchronized,就不再锁整个方法,性能上提示了一丢丢(注意这里其实是双重判断的懒汉模式,还有一个只有一层判断,因为和一开始的那个一样存在线程安全问题这里不做展示)

public class LazySingleton {

    // 注意:volatile关键字是必须的,防止指令重排序
    private static volatile LazySingleton instance;

    // 私有化构造方法,防止外部实例化
    private LazySingleton() {
    }

    // 获取单例实例的方法
    public static LazySingleton getInstance() {
        // 在第一次调用时才创建实例
        if (instance == null) {
            synchronized (LazySingleton.class) {
                // 注意这里使用的是双重判断,防止多线程并发时重复创建实例
                // 如果不加下面这个判断,多线程并发时,可能会创建多个实例
                if (instance == null) {
                    try {
                        // 模拟处理业务逻辑耗时
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(LazySingleton.getInstance().hashCode());
            }).start();
        }
    }
}

通过比较可以发现,饿汉模式是不管需不需要都会创建一个实例,有点浪费资源,然后在程序启动的时候会拖慢一点速度。懒汉模式虽然是在需要的时候才创建实例,但是因为使用了synchronized关键字,所以在使用的时候也会有性能问题。虽然问题都不大,但是有些完美主义可能就接受不了,所以下面我们再优化一下。

直接上代码(建议使用,目前来看应该是最完美的实现方式,唯一的缺点就是不能反序列化)
由于静态内部类只有在被使用的时候才会被加载,所以单例实例的创建会延迟到 getInstance() 方法被调用的时候。而且由于类加载过程是线程安全的,所以这种方式也是线程安全的。

public class Singleton {
    // 私有化构造方法,防止外部实例化
    private Singleton() {}

    // 静态内部类持有单例实例
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 获取单例实例的方法
    public static Singleton getInstance() {
        try {
            // 模拟处理业务逻辑耗时
            Thread.sleep(5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return SingletonHolder.INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(Singleton.getInstance().hashCode());
            }).start();
        }
    }
}

执行一下

在这里插入图片描述

那能不能写一个更完美的,让它能反序列化呢?答案当然是可以的!
直接上代码(虽然看起来很牛逼,用起来也很牛逼,但是不建议使用,违背Java代码设计原则)

public enum EnumSingleton {
    INSTANCE;

    // 注意枚举不是类没有构造方法
    // 这里可以用来处理业务逻辑
    public void doSomething() {
        System.out.println("do something");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                System.out.println(EnumSingleton.INSTANCE.hashCode());
            }).start();
        }
    }
}

执行一下

在这里插入图片描述

还有些其它的方式就不讲了,这几种基本就是最常见的,大家根据实际业务情况自行选择就好

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

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

相关文章

JVM垃圾回收之内存分配,死亡对象判断方法

Java 堆是垃圾收集器管理的主要区域&#xff0c;因此也被称作 GC 堆。 堆划分为新生代 老生代 永久代。 下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代&#xff0c;中间一层属于老年代&#xff0c;最下面一层属于永久代。 内存分配原则 对象优先在Eden区域分…

算法打卡day11

今日任务&#xff1a; 1&#xff09;239. 滑动窗口最大值 2&#xff09;347.前 K 个高频元素 239. 滑动窗口最大值 题目链接&#xff1a;239. 滑动窗口最大值 - 力扣&#xff08;LeetCode&#xff09; 给定一个数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移…

解锁人工智能新境界:大模型工程与架构的深度探索

在当今世界&#xff0c;人工智能(AI)无处不在&#xff0c;它已经悄然改变了我们的生活方式。从自动驾驶汽车到智能音箱&#xff0c;从智能医疗系统到虚拟助手&#xff0c;AI的影子无处不在。而在这一切背后&#xff0c;是一个个巨大的数学模型在默默运转。这些模型就像是我们人…

关于微信/支付宝支付

文章目录 前言一、多个支付怎么统一&#xff1f;二、支付调试支付宝支付注意 微信支付 总结 前言 最近搞了下微信支付&#xff0c;支付宝支付&#xff1b; 一、多个支付怎么统一&#xff1f; 这个很明显&#xff0c;通常直接用设计模式之-- 策略模式&#xff0c;对外公共提出…

把txt、pdf等文件转为一行一行的doccano数据集输入格式

文章目录 doccano 数据集导入简介代码实现代码运行结果代码公开 doccano 数据集导入 在Doccano 导入数据集时&#xff0c;使用TextLine的文件格式&#xff0c;导入的文件需要为一行一行文本的数据格式&#xff0c;每一行文本在导入Doccano后就是一条数据。 简介 主要工作说明…

Android Launcher开发注意事项

在开发Android Launcher时&#xff0c;需要关注性能、用户体验、权限管理、兼容性等方面&#xff0c;同时遵循相关的开发者政策和最佳实践。有几个重要的注意事项&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎…

iStoreOS使用体验

iStoreOS是OpenWRT改版而来的易用的软路由系统 我们知道OpenWRT还是有一定的上手难度的&#xff0c;对于小白要玩好openwrt就需要学习openwrt的扩容 和一些插件的安装&#xff0c;问题的拍错&#xff0c;需要一定的linux系统基础 而iStoreOS这个系统对于小白非常的优化 首先他…

【随笔】Git -- 解决提交时本地与目标分支不一致导致提交失败(三)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

教学管理与教育研究知网教育类G4期刊投稿发表简介

《教学管理与教育研究》杂志是由国家新闻出版总署批准&#xff0c;教育部主管&#xff0c;北京师范大学主办&#xff0c;北京师范大学出版社&#xff08;集团&#xff09;有限公司出版的教育类G4期刊。期刊面向全国学校教育的发展管理、教学研究、科研创新和师资培养等领域&…

档案著录员好干吗

档案著录员是负责对档案资料进行著录、整理和管理的专业人员。他们的工作主要包括&#xff1a; 1. 著录档案资料&#xff1a;根据相关规范和标准&#xff0c;对档案资料进行详细的著录&#xff0c;包括档号、题名、日期、责任者、关键词等信息&#xff0c;以便于后续的检索和利…

用 Open-Sora 高效创作视频,让创意触手可及

近年来&#xff0c;视频内容以爆炸式增长席卷了我们的生活。从短视频平台到直播带货&#xff0c;视频正成为人们获取信息和娱乐的主要方式。然而&#xff0c;传统视频制作流程往往耗时费力&#xff0c;对于普通用户来说门槛较高。 为了降低视频创作门槛&#xff0c;让更多人享…

会声会影2023新版本特点以及会声会影2023序列号注册机keygen下载

会声会影简介 虽然现在已经是2024年了&#xff0c;但是大家对会声会影2024的热爱一直不减&#xff0c;很多人后台问我&#xff0c;有没有会声会影2023序列号和注册机&#xff0c;这不&#xff0c;今天这篇文章它来了。 会声会影2023新版特性 1.全新的进入/中场/退出标题动态功…

YOLOv9运行报错:AttributeError: ‘FreeTypeFont‘ object has no attribute ‘getsize‘

在运行yolov9时&#xff0c;报错&#xff1a;attributeerror: ‘FreeTypeFont’ object has no attribute ‘getsize’ 在网上搜索的结果大部分给出字体不对之类的答案但通过仔细研究&#xff0c;发现该报错的报错原因不是字体不对&#xff0c;而是因为安装了新版本的 Pillow…

小白也可以轻松学大模型 RAG:FlagEmbedding 重排序

RAG模型已经取得了显著的进展&#xff0c;但其性能仍然受到排序质量的限制。在实践中&#xff0c;我们发现重排序技术能够有效地改善排序的效果&#xff0c;从而进一步提升RAG模型在问答任务中的表现。 重排序的作用 与传统的嵌入模型不同&#xff0c;重排序器&#xff08;rera…

SpringBoot整合Redis:Redis优化解决数据一致性问题

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏Redis实战与进阶 本专栏讲解Redis从原理到实践 …

3.21总结​.Codeforces 第 935 轮(第 3 组)(A~D)题解​

一.Codeforces 第 935 轮&#xff08;第 3 组&#xff09;(A~D)题解 (直接给原文了,我电脑的翻译就是一坨.......) 这到题是一道思维题,理解题目意思就会觉得很简单首先内向的人肯定是一个人一个帐篷,我们只要关心外向的人就可以了,我们只要分外向的人可不可以被3整除,分成两种…

fyne时间选择框-自定义组件,带绑定

实现的效果如图 自定义组件代码&#xff1a; package widgetimport ("fmt""fyne.io/fyne/v2""fyne.io/fyne/v2/container""fyne.io/fyne/v2/widget""strconv""time" )var timeLayout "15:04"type Se…

2024临床常用的心衰评估量表,医生必备!

心衰评估的相关量表&#xff0c;是临床常用的量表类型。常笑医学整理了6个常用的心衰评估量表&#xff0c;包括NYHA心功能分级、Killip分级、2型糖尿病的心衰风险评分、心衰院内死亡率评分、急性失代偿期心衰住院患者6个月死亡率评估、心衰患者1年死亡率评估。这些心衰评估量表…

找不到msvcp110.dll无法继续执行程序的多种解决方法

在计算机操作系统中&#xff0c;msvcp110.dll文件扮演着至关重要的角色。作为Microsoft Visual C Redistributable Package的一部分&#xff0c;这个特定的dll文件包含了系统运行多种应用程序所必需的关键运行时库函数。许多Windows应用程序依赖于msvcp110.dll文件来执行基本操…

2024年【熔化焊接与热切割】模拟考试题库及熔化焊接与热切割实操考试视频

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 熔化焊接与热切割模拟考试题库参考答案及熔化焊接与热切割考试试题解析是安全生产模拟考试一点通题库老师及熔化焊接与热切割操作证已考过的学员汇总&#xff0c;相对有效帮助熔化焊接与热切割实操考试视频学员顺利通…