当前位置: 首页 > news >正文

Java设计模式-单例模式最佳实践

在这里插入图片描述

1. 单例模式简介

Java 单例模式是四大设计模式之一,属于创建型设计模式。从定义上看,它似乎是一种简单的设计模式,但在实现时,如若不注意,它会带来很多问题。

在本文中,我们将了解单例设计模式原则,探索实现单例设计模式的不同方法,以及一些最佳实践。

2. 单例模式原理

  • 单例模式限制类的实例化,并确保 Java 虚拟机中只存在该类的一个实例。
  • 单例类必须提供一个全局访问点来获取该类的实例。
  • 单例模式用于日志记录驱动程序对象缓存线程池
  • 单例设计模式也用于其他设计模式,如抽象工厂生成器原型外观等。
  • 单例设计模式也用于核心 Java 类(例如,java.lang.Runtimejava.awt.Desktop

3. 单例模式实现方式

为了实现单例模式,我们有不同的方法,但它们都有以下共同的概念

  • 私有构造函数用于限制其他类对该类的实例化。
  • 同一类的私有静态变量,是该类的唯一实例
  • 返回类的实例的公共静态方法,这是外界获取单例类实例的全局访问点

在这里插入图片描述

3.1 急切初始化

急切初始化中,单例类的实例是在类加载时创建的。急切初始化的缺点是即使客户端应用程序可能没有使用该方法,也会创建该实例。以下是静态初始化单例类的实现:

public class EagerInitializedSingleton {private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();// private constructor to avoid client applications using the constructorprivate EagerInitializedSingleton(){}public static EagerInitializedSingleton getInstance() {return instance;}
}

如果您的单例类没有使用大量资源,则可以使用这种方法。但在大多数情况下,单例类是为文件系统、数据库连接等资源创建的。除非客户端调用该getInstance方法,否则我们应该避免实例化。此外,此方法不提供任何异常处理选项。

3.2 静态块初始化

静态块初始化实现与急切初始化类似,不同之处在于类的实例是在静态块中创建的,并提供了异常处理的选项。

public class StaticBlockSingleton {private static StaticBlockSingleton instance;private StaticBlockSingleton(){}// static block initialization for exception handlingstatic {try {instance = new StaticBlockSingleton();} catch (Exception e) {throw new RuntimeException("Exception occurred in creating singleton instance");}}public static StaticBlockSingleton getInstance() {return instance;}
}

注意:立即初始化和静态块初始化都会在使用之前创建实例,这不是最佳做法

3.3 延迟初始化

实现单例模式的惰性初始化方法在全局访问方法中创建实例。以下是使用此方法创建单例类的示例代码:

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

上述实现在单线程环境中运行良好,但在多线程系统中,如果if同时有多个线程处于条件中,则可能会导致问题。它将破坏单例模式,并且两个线程将获得单例类的不同实例。在下一节中,我们将看到创建线程安全的单例类的不同方法。

3.4 线程安全单例

在这里插入图片描述
创建线程安全的单例类的一个简单方法是将全局访问方法同步,以便一次只有一个线程可以执行此方法。以下是此方法的一般实现:

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

上述实现工作正常,并且提供了线程安全性,但由于与同步方法相关的成本,它降低了性能,尽管我们只需要在可能创建单独实例的前几个线程中使用该方法。为了避免每次都产生这种额外的开销,使用了双重检查锁定if原则。在这种方法中,同步块用于条件中,并进行额外检查以确保只创建了一个单例类的实例。以下代码片段提供了双重检查锁定实现

public static ThreadSafeSingleton getInstanceUsingDoubleLocking() {if (instance == null) {synchronized (ThreadSafeSingleton.class) {if (instance == null) {instance = new ThreadSafeSingleton();}}}return instance;
}

以上代码的同步锁,仅仅加到了if (instance == null) {这个判断下面,也就是只有当实例未初始化时,才到这里面同步去初始化,也就是并发只有在未初始化那一短暂时刻才会发生,极大降低了同步锁带来的并发开销!视为最佳方式!

3.5 Bill Pugh 单例实现

在 Java 5 之前,Java 内存模型存在很多问题以前的方法在某些情况下会失败,因为太多线程同时尝试获取单例类的实例。因此,Bill Pugh提出了一种不同的方法,使用内部静态辅助类来创建单例类。以下是 Bill Pugh Singleton 实现的一个示例:

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

注意包含单例类实例的私有内部静态类。加载单例类时,SingletonHelper该类不会加载到内存中,只有当有人调用该getInstance()方法时,才会加载该类并创建单例类实例。这是单例类最广泛使用的方法,因为它不需要同步。

3.6 使用反射来破坏单例模式

反射可以用来破坏之前所有的单例实现方式。下面是一个示例类:

import java.lang.reflect.Constructor;public class ReflectionSingletonTest {public static void main(String[] args) {EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();EagerInitializedSingleton instanceTwo = null;try {Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();for (Constructor constructor : constructors) {// This code will destroy the singleton patternconstructor.setAccessible(true);instanceTwo = (EagerInitializedSingleton) constructor.newInstance();break;}} catch (Exception e) {e.printStackTrace();}System.out.println(instanceOne.hashCode());System.out.println(instanceTwo.hashCode());}
}

运行上述测试类时,您会注意到hashCode两个实例并不相同,这会破坏单例模式。反射非常强大,在 Spring 和 Hibernate 等许多框架中都有使用。继续学习Java 反射教程。

3.7 枚举单例

为了通过反射克服这种情况,Joshua Bloch建议使用 来enum实现单例设计模式,因为 Java 确保任何enum值在 Java 程序中只实例化一次。由于Java 枚举值是全局可访问的,因此单例也是如此。缺点是该enum类型有些不灵活(例如,它不允许延迟初始化)。

public enum EnumSingleton {INSTANCE;public static void doSomething() {// do something}
}

3.8 序列化和单例

有时在分布式系统中,我们需要Serializable在单例类中实现接口,以便我们可以将其状态存储在文件系统中并在以后的某个时间点检索它。这是一个Serializable也实现接口的小单例类:

import java.io.Serializable;public class SerializedSingleton implements Serializable {private static final long serialVersionUID = -7604766932017737115L;private SerializedSingleton(){}private static class SingletonHelper {private static final SerializedSingleton instance = new SerializedSingleton();}public static SerializedSingleton getInstance() {return SingletonHelper.instance;}
}

序列化单例类的问题在于,每当我们反序列化它时,它都会创建该类的新实例。以下是一个例子:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;public class SingletonSerializedTest {public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {SerializedSingleton instanceOne = SerializedSingleton.getInstance();ObjectOutput out = new ObjectOutputStream(new FileOutputStream("filename.ser"));out.writeObject(instanceOne);out.close();// deserialize from file to objectObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();in.close();System.out.println("instanceOne hashCode="+instanceOne.hashCode());System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());}
}
Output
instanceOne hashCode=2011117821
instanceTwo hashCode=109647522

所以它破坏了单例模式。为了克服这种情况,我们需要做的就是提供readResolve()方法的实现。

protected Object readResolve() {return getInstance();
}

此后,您会注意到hashCode测试程序中的两个实例是相同的

4. 总结

好啦,以上就是关于java设计模式的单例模式详细介绍,整理出八种初始化方式及其利弊分析,最佳方式推荐是3.4章节的线程安全单例

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 第26课 Scratch入门篇:乘坐公交车
  • 服务器CPU天梯图2024年8月,含EYPC/至强及E3/E5
  • 使用 Java Swing 创建一个最大公约数计算器 GUI 应用
  • 【Linux】输入输出重定向
  • vue3组件之间通讯
  • 华为OD-D卷游戏分组
  • keepalived+lvs高可用负载均衡集群配置方案
  • MATLAB算法实战应用案例精讲-【数模应用】均值z 检验(附R语言、python和MATLAB代码实现)
  • Otter Go 语言编写的非竞争式缓存库
  • 8月5日学习笔记 glibc安装与安全用户角色权限
  • Postgresql数据库密码忘记的解决
  • 操作系统中的进程:深入解析与理解
  • Qt+TSC打印机调试
  • springboot疫情居家办公系统--论文源码调试讲解
  • AI驱动的招聘流程革新与挑战应对
  • “大数据应用场景”之隔壁老王(连载四)
  • CEF与代理
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • JSONP原理
  • react 代码优化(一) ——事件处理
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • TypeScript实现数据结构(一)栈,队列,链表
  • uva 10370 Above Average
  • 编写符合Python风格的对象
  • 反思总结然后整装待发
  • 前嗅ForeSpider采集配置界面介绍
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 收藏好这篇,别再只说“数据劫持”了
  • 微信小程序:实现悬浮返回和分享按钮
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 积累各种好的链接
  • 选择阿里云数据库HBase版十大理由
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • #{} 和 ${}区别
  • #13 yum、编译安装与sed命令的使用
  • #LLM入门|Prompt#3.3_存储_Memory
  • #微信小程序(布局、渲染层基础知识)
  • #在 README.md 中生成项目目录结构
  • ${factoryList }后面有空格不影响
  • (16)Reactor的测试——响应式Spring的道法术器
  • (3)选择元素——(17)练习(Exercises)
  • (Python) SOAP Web Service (HTTP POST)
  • (八)Flink Join 连接
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (二)c52学习之旅-简单了解单片机
  • (分布式缓存)Redis分片集群
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (九)信息融合方式简介
  • (每日一问)计算机网络:浏览器输入一个地址到跳出网页这个过程中发生了哪些事情?(废话少说版)
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (十七)Flink 容错机制
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (一)appium-desktop定位元素原理