Java设计模式:单例模式之六种实现方式详解(二)

在Java中,单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式在多种场景下都很有用,比如配置文件的读取、数据库连接池、线程池等。本文将详细介绍Java中实现单例模式的六种方式,并分析它们的原理和优缺点。

目录

      • 单例模式概述
      • 1. 饿汉式
      • 2. 懒汉式(线程不安全)
      • 3. 懒汉式(线程安全)
      • 4. 双重检查锁定(DCL)
      • 5. 静态内部类
      • 6. 枚举
      • 单例模式的使用场景
      • 注意事项
      • 总结

单例模式概述

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目标是确保一个类只有一个实例,并提供一个全局访问点来获取该实例。在单例模式中,类的构造函数通常是私有的,以防止其他类实例化它。同时,该类提供一个静态方法或属性来获取该类的唯一实例。

单例模式的应用场景包括需要频繁进行创建和销毁的对象、需要共享资源的情况以及需要全局唯一访问点的场景等。通过使用单例模式,可以节省系统资源、提高性能并避免对共享资源的多重占用。

在Java等面向对象的编程语言中,单例模式通常通过以下方式实现:

  • 饿汉式单例(在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快
  • 懒汉式单例(类加载时不初始化,第一次调用时才初始化,并且需要考虑线程安全的问题)
  • 双重检查锁定(DCL,即懒汉式的优化,可以减少部分不必要的同步)
  • 静态内部类(利用了classloader的机制来保证初始化instance时只有一个线程
  • 枚举(不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象)等。

使用单例模式时需要注意线程安全、反序列化问题、反射攻击等问题。此外,在设计时也需要考虑其可扩展性,以便在未来需要支持多个实例或动态创建实例时能够方便地进行修改。

1. 饿汉式

原理:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。因为类加载时就完成了初始化,所以天生就是线程安全的。

public class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}

优点:实现简单,线程安全。

缺点:如果单例从未被使用,则会造成内存的浪费。

2. 懒汉式(线程不安全)

原理:类加载时不初始化,第一次调用getInstance()方法时才创建实例。这种方式在多线程环境下是不安全的,可能会导致创建多个实例。

public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

优点:避免了饿汉式的内存浪费问题。

缺点:线程不安全,可能导致创建多个实例。

3. 懒汉式(线程安全)

原理:在getInstance()方法上加同步锁,确保在多线程环境下只创建一个实例。

public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

优点:线程安全。

缺点:每次调用getInstance()方法时都要进行同步,效率较低。

4. 双重检查锁定(DCL)

原理:通过双重检查锁定(DCL)来减少同步的开销。只有当instancenull时,才进行同步块的加锁操作。

public class Singleton {private volatile static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

注意:在Java 1.5以前的版本中,双重检查锁定由于JVM的内存模型原因可能会出现问题,但在Java 1.5及以上版本中,通过volatile关键字和内存模型的改进,双重检查锁定已经被正确地实现。

优点:线程安全,且效率较高。

缺点:实现相对复杂,容易出错。

5. 静态内部类

原理:利用静态内部类的特性,当外部类被加载时,静态内部类不会被加载,只有当调用静态内部类时才会被加载,从而实现了懒加载。同时,静态内部类的加载是线程安全的。

public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

优点:线程安全,且实现了懒加载。

缺点:与饿汉式相比,增加了一定的代码复杂性。

6. 枚举

原理:利用枚举的特性,Java枚举在创建时是线程安全的,并且只会装载一次。

当使用枚举来实现单例模式时,代码实际上非常简单和直接。枚举在Java中是一种特殊的类,它有一组预定义的常量。由于Java枚举的特性,它们在创建时是线程安全的,并且只会装载一次,这使得它们成为实现单例模式的一种有效方式。

public enum Singleton {// 枚举常量,它本身就是单例的实例INSTANCE;// 单例对象可以拥有方法public void someMethod() {// 这里是单例对象可以执行的操作System.out.println("Doing something in the singleton instance.");}
}// 使用示例
public class SingletonDemo {public static void main(String[] args) {// 获取单例对象Singleton singleton = Singleton.INSTANCE;// 调用单例对象的方法singleton.someMethod();// 尝试再次获取单例对象,将会得到与之前相同的实例Singleton anotherSingleton = Singleton.INSTANCE;// 检查两个引用是否指向同一个对象System.out.println("singleton == anotherSingleton: " + (singleton == anotherSingleton));}
}

在这个例子中,Singleton是一个枚举类,它只有一个枚举常量INSTANCE。这个常量就是单例对象的唯一实例。你可以在Singleton枚举类中定义任何你需要的方法,就像在一个普通的Java类中那样。

当你通过Singleton.INSTANCE来访问单例对象时,Java虚拟机保证你总是得到同一个实例,因为枚举常量在加载时就被创建,并且是唯一的。

使用枚举实现单例模式的好处之一是它自动支持序列化机制,即使你将枚举实例序列化后再反序列化,得到的仍然是同一个实例。这是由Java枚举的序列化特性保证的。此外,枚举方式还防止了反射攻击,因为尝试通过反射来调用枚举的私有构造器将会抛出异常。

使用方式Singleton.INSTANCE.someMethod();

优点:实现简单,线程安全,且自动支持序列化机制,防止反序列化重新创建新的对象。

缺点:无法进行懒加载。如果其他单例模式的实现有序列化需求时,需要注意防止反序列化导致的重新创建对象的问题。而枚举类型则没有这个问题。

单例模式的使用场景

  1. 全局访问点:当需要提供一个全局唯一的访问点来访问某个资源或服务时,单例模式非常适用。例如,配置文件的管理、数据库连接池、线程池、日志记录器等。

  2. 资源控制:单例模式可用于控制对共享资源的并发访问,如文件、网络连接或数据库连接。通过确保只有一个实例存在,可以避免不必要的资源消耗和冲突。

  3. 状态维护:如果需要在整个应用程序生命周期中维护某个状态或信息,并且这个状态不会因多个实例的创建而改变,那么单例模式是一个很好的选择。

  4. 节省系统资源:对于创建开销较大的对象或资源密集型对象,使用单例模式可以确保只创建一个实例,从而节省系统资源。

  5. 工具类:一些工具类,如数学计算工具、日期格式化工具等,通常不需要多个实例,可以使用单例模式来实现。

注意事项

  1. 线程安全:在多线程环境下,确保单例模式的实现是线程安全的。可以使用双重检查锁定(DCL)、静态内部类、枚举等方式来实现线程安全的单例。

  2. 反序列化问题:如果单例对象实现了Serializable接口,那么需要注意反序列化时可能会创建新的实例。为了解决这个问题,可以在readResolve()方法中返回单例对象。

  3. 反射攻击:尽管Java语言提供了访问修饰符来限制类的实例化,但通过反射机制仍然可以调用私有构造器。因此,需要注意防止通过反射破坏单例模式的约束。可以通过在私有构造器中检查已存在实例并抛出异常来防止这种情况。

  4. 垃圾回收:如果单例对象持有对外部对象的引用,并且这些外部对象不再需要时没有被正确释放,那么可能会导致内存泄漏。因此,需要确保单例对象在不再需要时能够释放其持有的资源。

  5. 测试问题:在单元测试中,单例模式可能会导致测试之间的依赖和顺序问题。为了避免这种情况,可以考虑使用依赖注入或模拟框架来替换单例对象。

  6. 可扩展性:设计单例模式时需要考虑其可扩展性。如果需要支持多个实例或动态创建实例,那么单例模式可能不是最佳选择。在这种情况下,可以考虑使用工厂模式或原型模式等替代方案。

总结

在选择单例模式的实现方式时,需要根据具体的应用场景和需求进行权衡。如果单例对象在程序启动时就需要被创建且不会造成内存浪费,可以选择饿汉式;如果需要实现懒加载,并且对线程安全性有要求,可以选择静态内部类或枚举;如果需要在懒加载的同时还要追求极致的性能,可以尝试双重检查锁定,但需要注意其实现的复杂性。懒汉式(线程不安全)通常不推荐使用,因为它在多线程环境下可能会导致问题。懒汉式(线程安全)虽然简单,但由于其效率较低,也不常作为首选方案。

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

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

相关文章

组合模式(Composite Pattern)

定义 组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构,并且能像使用单独对象一样使用组合对象。组合模式让客户端代码对单个对象和复合对象的使用具有一致性。 在组合模式中,我们定义…

Spring Boo项目中方法参数对象中字段上存在的自定义注解如何进行拦截解析

一、前言 在Spring Boot项目开发过程中,我们经常会使用到自定义注解的方式进行业务逻辑开发,此时注解我们一般是放在方法或者类上面,通过AOP切面拦截的方式进行自定义业务逻辑填充。但是如果自定义注解放在类的字段上,此时应该如…

Clickhouse填坑记4:Too many parts问题分析

Clickhouse在进行大数据量同步时,感觉很爽,插入速度非常快,但是,在使用过程中却出现了几次“Too many parts”异常报错,搞得很痛苦,这里记录一下解决过程。 我这边采用的是Flink程序,实时将数据写入ClickHouse,在执行一段时间后,会提示“Too many parts”异常,如下异…

云端技术驾驭DAY13——Pod污点、容忍策略、Pod优先级与抢占、容器安全

往期回顾: 云端技术驾驭DAY01——云计算底层技术奥秘、云服务器磁盘技术、虚拟化管理、公有云概述 云端技术驾驭DAY02——华为云管理、云主机管理、跳板机配置、制作私有镜像模板 云端技术驾驭DAY03——云主机网站部署、web集群部署、Elasticsearch安装 云端技术驾驭…

XXE 漏洞简单研究

近期在做个基础的 web 常见漏洞的 ppt,主要参考 OWASP TOP 10 2017RC2,此版本中增加了 XXE 攻击,所以自己简单的研究下 XXE 攻击。XXE(XML External Entity)XML 外部实体,当前端和后端通信数据采用 xml&…

element el-date-picker 日期组件置灰指定日期范围、禁止日期范围日期选择

JS如何将当前日期或指定日期转时间戳_javascript技巧_脚本之家 小于指定日期前的日期置灰 比如这里 指定日期是 2024-02-20 10:48:15 disabledDate(time) time是一个函数提供的时间用于比较 他是一个时间戳↓ 理解为我们想要置灰的时间 time.getTime() < timeStamps- 1 *…

《Large Language Models for Generative Information Extraction: A Survey》阅读笔录

论文地址&#xff1a;Large Language Models for Generative Information Extraction: A Survey 前言 映像中&#xff0c;比较早地使用“大模型“”进行信息抽取的一篇论文是2022年发表的《Unified Structure Generation for Universal Information Extraction》&#xff0c;也…

IDEA开发环境热部署

开发环境热部署 在实际的项目开发调试过程中会频繁地修改后台类文件&#xff0c;导致需要重新编译重新启动&#xff0c;整个过程非常麻烦&#xff0c;影响开发效率。Spring Boot提供了spring-boot-devtools组件&#xff0c;使得无须手动重启SpringBoot应用即可重新编译、启动项…

cesium相机视角跳转和缩放至entity方法汇总

下面汇总的相机视角跳转方法有很多种&#xff0c;都是根据某一个经纬度高程坐标[x,y,z]&#xff0c;Entity实体或者矩形四至范围[west , south , east , north]作为视图跳转到目标范围的中心&#xff0c;然后在该位置上设置相机相对的偏移量&#xff0c;这个偏移量主要是设置相…

36.云原生之SpringCloud+k8s实践

云原生专栏大纲 文章目录 SpringCloudk8s介绍spring-cloud-kubernetes服务发现配置管理负载均衡选主 spring-cloud-bookinfo案例构建项目环境配置namespace部署与验证productpagegatewaybookinfo-admindetailsratingsreviewsreviews-v1reviews-v2 总结 SpringCloudk8s介绍 ht…

vue-router4 (六) 路由嵌套

应用场景&#xff1a; ①比如京东页面的首页、购物车、我的按钮&#xff0c;可以点击切换到对应的页面&#xff1b; ② 比如 Ant Design左侧这些按钮点击就会切到对应的页面&#xff0c;此时可以把左侧按钮放在父路由中&#xff0c;右侧的子路由 1.路由配置&#xff0c;子路由…

将法律条文很美观的复制到word上

前言 目前很多法律条款都没有现成的PDF或者word格式的供大家下载&#xff0c;这个时候呢&#xff0c;领导又要求你帮他搞定&#xff0c;这就很。。。。 步骤 复制全部条款到word中使用wps的排版功能&#xff0c;将空格和空段落全部移除 3. 设置好你需要的格式 标题&#xff…

常用对象的遍历方法

var obj [{name: 1111,account: {01: { name: 1.1 },02: { name: 1.2 },03: { name: 1.3 },04: { name: 1.4 },05: { name: 1.5 },}} ]var nowObj obj[0].account;1、for…in 任意顺序遍历对象所有的可枚举属性&#xff08;包括对象自身的和继承的可枚举属性&#xff0c;不含…

影像仪激光扫描功能,无缝连接2D/3D混合测量

在现代工业生产领域&#xff0c;影像仪用于质量控制和产品检测&#xff0c;是一个不可或缺的工具。它通过高精度的成像和图像处理技术&#xff0c;可以及时发现产品的缺陷和异常&#xff0c;以保证产品质量的稳定性和一致性。 影像仪的重要性及其面临的挑战 在工业生产方面&a…

视频生成模型(猜测sora)demo

import numpy as np import paddle import paddle.nn as nn import paddle.nn.functional as Fclass FeedForward(nn.Layer):def __init__(self, hidden_dim)

代码随想录算法训练营第42天|● 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

文章目录 1049.最后一块石头的重量II思路&#xff1a;动归五部曲代码&#xff1a; ● 494. 目标和思路五部曲1.确定dp数组五部曲2.确定dp公式3.dp初始化4.遍历顺序 代码&#xff1a; ● 474.一和零思路动归五部曲 代码&#xff1a; 1049.最后一块石头的重量II 思路&#xff1a;…

go test用法(获取单元测试覆盖率)

go test用法&#xff08;获取ut覆盖率&#xff09; 为了提升系统的稳定性&#xff0c;一般公司都会对代码的单元测试覆盖率有一定要求。下面针对golang自带的测试命令go test做讲解。 1 命令 1.1 go test ./… &#xff08;运行当前目录及所有子目录下的测试用例&#xff09; …

ubuntu——解决复制粘贴问题

执行以下四步&#xff1a; ①更新源 sudo apt update ②使用APT&#xff08;高级包管理工具&#xff09;来自动移除系统中未使用的依赖包&#xff0c;并且将open-vm-tools软件包及其相关的未使用的依赖包一并移除。 sudo apt-get autoremove open-vm-tools 注&#xff1a;…

【virtual Box】功能速通:安装 Windows 和 Ubuntu

文章目录 一、虚拟机1.1 概述1.2 virtual box概述 二、新建虚拟机、删除、注册三、虚拟机内部设置3.1 安装增强功能驱动3.2 分辨率问题3.3 网络链接方式 一、虚拟机 1.1 概述 虚拟机&#xff08;Virtual Machine&#xff0c;VM&#xff09;是一种软件实现的计算机系统&#x…

【Nginx笔记02】通过Nginx服务器转发客户端的WebSocket接口到后端服务

这篇文章&#xff0c;主要介绍如何通过Nginx服务器转发客户端的WebSocket接口到后端服务【知识星球】。 目录 一、Nginx配置WebSocket 1.1、Nginx配置内容 1.2、客户端请求地址 1.3、创建WebSocket测试工程 1.4、启动测试 1.5、WebSocket超时问题 1.5.1、设置超时时间 …