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

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

这章主要讲单例模式,也是最简单的一种模式,但是因为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();
        }
    }
}

执行一下

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

车载测试项目实践 USD诊断 CANoe工具使用

本周末2天的时间&#xff0c;可以线下带大家对车载项目&#xff1a; uds诊断进行实操训练和CANoe工具的灵活使用 本博主从事新能源汽车的研发部&#xff0c;主要是嵌入式方面的&#xff0c;对车载测试的底层逻辑非常熟悉。 需要项目或者CANoe工具实操的可以关注并私信我

解决长尾问题,BEV-CLIP:自动驾驶中复杂场景的多模态BEV检索方法

解决长尾问题&#xff0c;BEV-CLIP&#xff1a;自动驾驶中复杂场景的多模态BEV检索方法 理想汽车的工作&#xff0c;原文&#xff0c;BEV-CLIP: Multi-modal BEV Retrieval Methodology for Complex Scene in Autonomous Driving 链接&#xff1a;https://arxiv.org/pdf/2401.…

算法思想总结:位运算

创作不易&#xff0c;感谢三连支持&#xff01;&#xff01; 一、常见的位运算总结 标题 二、位1的个数 . - 力扣&#xff08;LeetCode&#xff09; 利用第七条特性&#xff1a;n&&#xff08;n-1&#xff09;干掉最后一个1&#xff0c;然后每次都用count去统计&#xff…

MySQL数据库的下载和安装以及命令行语法学习

MySQL数据库的下载和安装以及命令行语法学习 学习MYSQL&#xff0c;掌握住基础的SQL句型&#xff08;创建数据库、查看数据库列表、数据增、删、改、查等操作类型&#xff09; 首先要知道MySQL下载和安装方法&#xff1a; 提示&#xff1a;别嫌啰嗦&#xff0c;对于一个初识MY…

vue-cli3中拉取vue-cli2

vue-cli3中拉取vue-cli2 拉取 2.x 模板 (旧版本) Vue CLI > 3 和旧版使用了相同的 vue 命令&#xff0c;所以 Vue CLI 2 (vue-cli) 被覆盖了。如果你仍然需要使用旧版本的 vue init 功能&#xff0c;你可以全局安装一个桥接工具&#xff1a; npm install -g vue/cli-init…

linux源配置:ubuntu、centos;lspci与lsmod命令区别

1、ubuntu源配置 1&#xff09;先查电脑版本型号: lsb_release -c2&#xff09;再编辑源更新&#xff0c;源要与上面型号对应 参考&#xff1a;https://midoq.github.io/2022/05/30/Ubuntu20-04%E6%9B%B4%E6%8D%A2%E5%9B%BD%E5%86%85%E9%95%9C%E5%83%8F%E6%BA%90/ /etc/apt/…

MUNIK第二届功能安全及自动驾驶研讨会将在沪召开

2024年4月26日,由上海秒尼科技术服务有限公司(以下简称“Munik”)联合Parosoft主办的“第二届功能安全及自动驾驶研讨会”将在上海虹桥隆重开幕。 据了解,本次功能与自动驾驶安全研讨会,将聚焦在ISO 26262标准体系下,自动驾驶新形势下各个零部件供应商如何满足功能安全等相关重…

移除和替换任何内容:AI 驱动的图像修复工具 | 开源日报 No.204

Sanster/IOPaint Stars: 15.1k License: Apache-2.0 IOPaint 是一款由 SOTA AI 模型驱动的图像修复工具。 该项目解决了从图片中移除任何不需要的对象、瑕疵或人物&#xff0c;以及擦除和替换图片上任何内容&#xff08;由稳定扩散技术支持&#xff09;的问题。 完全免费且开…

赋能数据收集:从机票网站提取特价优惠的JavaScript技巧

背景介绍 在这个信息时代&#xff0c;数据的收集和分析对于旅游行业至关重要。在竞争激烈的市场中&#xff0c;实时获取最新的机票特价信息能够为旅行者和旅游企业带来巨大的优势。 随着机票价格的频繁波动&#xff0c;以及航空公司和旅行网站不断推出的限时特价优惠&#xff…

【Java - 框架 - HttpClient 5】(01) HttpClient 5 使用详细教程,代码示例 - 快速上手

HttpClient 5 使用详细教程&#xff0c;代码示例 - 快速上手 依赖 【Maven依赖】 <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5 --> <dependency><groupId>org.apache.httpcomponents.client5</groupId>…

网络分析(蓝桥杯,acwing,并查集)

题目描述&#xff1a; 小明正在做一个网络实验。 他设置了 n 台电脑&#xff0c;称为节点&#xff0c;用于收发和存储数据。 初始时&#xff0c;所有节点都是独立的&#xff0c;不存在任何连接。 小明可以通过网线将两个节点连接起来&#xff0c;连接后两个节点就可以互相通…

JAVAEE——多线程的设计模式,生产消费模型,阻塞队列

文章目录 多线程设计模式什么是设计模式单例模式饿汉模式懒汉模式线程安全问题懒汉模式就一定安全吗&#xff1f;锁引发的效率问题jvm的优化引起的安全问题 阻塞队列阻塞队列是什么&#xff1f;生产消费者模型阻塞队列实现消费生产者模型可能遇到的异常 多线程设计模式 什么是…

【PHP + 代码审计】数组函数

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

安全工具介绍 SCNR/Arachni

关于SCNR 原来叫Arachni 是开源的&#xff0c;现在是SCNR&#xff0c;商用工具了 可试用一个月 Arachni Web Application Security Scanner Framework 看名字就知道了&#xff0c;针对web app 的安全工具&#xff0c;DASTIAST吧 安装 安装之前先 sudo apt-get update sudo…

力扣面试150 阶乘后的零 数论 找规律 质因数

Problem: 172. 阶乘后的零 思路 &#x1f468;‍&#x1f3eb; 大佬神解 一个数末尾有多少个 0 &#xff0c;取决于这个数 有多少个因子 10而 10 可以分解出质因子 2 和 5而在阶乘种&#xff0c;2 的倍数会比 5 的倍数多&#xff0c;换而言之&#xff0c;每一个 5 都会找到一…

AWTK T9 输入法实现原理

1. T9 输入法的中文字典数据 网上可以找到 T9 输入法的中文字典数据&#xff0c;但是通常有两个问题&#xff1a; 采用 GPL 协议&#xff0c;不太适合加入 AWTK。 只支持单个汉字的输入&#xff0c;不支持词组的输入。 经过考虑之后&#xff0c;决定自己生成 T9 输入法的中…

选择器加练习

一、常用的选择器 1.元素选择器 语法 : 标签名{} 作用 : 选中对应标签中的内容 例:p{} , div{} , span{} , ol{} , ul{} ...... 2.类选择器(class选择器) 语法 : .class属性值{} 作用 : 选中对应class属性值的元素 注意:class里面的属性值不能以数字开头,如果以符号开头,…

基于python+vue城市交通管理系统的设计与实现flask-django-php-nodejs

此系统设计主要采用的是python语言来进行开发&#xff0c;采用django/flask框架技术&#xff0c;框架分为三层&#xff0c;分别是控制层Controller&#xff0c;业务处理层Service&#xff0c;持久层dao&#xff0c;能够采用多层次管理开发&#xff0c;对于各个模块设计制作有一…

Docker 镜像仓库

目录 1、搭建私有 registry 服务端创建镜像仓库 客户端推送镜像 镜像导入导出 2、Nginx 代理 registry 仓库 SSL 证书 & https 协议 SSL证书 https协议 SSL 的验证流程 客户端安装 Nginx 使用 openssl 生成CA根证书和根证书key 创建 Nginx 服务证书 配置启动 N…

线段树和树状数组

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 树状数组和线段树1.树状数组1.1动态求连续区间和1.2数…