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

深入了解Java单例模式及其使用场景

什么是单例模式?

简单通俗来讲,单例模式是一种设计模式,它确保一个类只能有一个实例,并提供全局访问。在 Java 中,可以通过以下几种方式来实现线程安全的单例模式。

饿汉式单例

饿汉式单例模式指的是在类加载的时候就创建单例对象。这种方式的实现比较简单,只需要在类的定义中添加一个静态成员变量,该成员变量用于保存单例对象,同时将类的构造方法私有化,以防止外部创建对象。

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

注意:

1. 饿汉式单例与懒汉式单例的区别在于对象的创建时间。饿汉式单例在类加载时就已经创建了单例对象,并且它是由static final 修饰的,保证了单例对象在整个程序中都只有一个实例,所以不会有性能问题,并且也不需要额外的同步操作来保证线程安全。所以饿汉式单例天生就是线程安全的。但是饿汉式单例在类加载时就创建了单例对象,因此在某些场景下可能会造成内存浪费。饿汉单例模式适用于单例对象创建开销不大,并且程序启动时需要使用单例对象的场景。

例如,在程序启动时就需要加载的配置信息,可以使用饿汉式单例来读取配置文件。因为在程序启动时就需要加载配置文件,而且配置文件只需要读取一次,所以可以使用饿汉式单例。

一个经典的应用场景就是在 Java 中的日志管理类,下面是一个简单的实现:

public class Logger {private static final Logger INSTANCE = new Logger();private Logger(){//初始化}public static Logger getInstance(){return INSTANCE;}public void log(String message){//执行日志记录}
}

这个Logger 类负责管理程序中所有的日志信息,因为在类加载时已经创建了 Logger 对象,所以在整个程序中只有一个 Logger 对象,并且是线程安全的。这样使用时只需要调用Logger.getInstance().log("log message")来记录日志。

还有一个特殊的场景就是在资源限制的情况下,比如设备硬件限制,或者是其他限制条件下,这种情况下因为不能等待长时间创建对象,又因为每个线程都可能会请求单例对象,所以此时就可以使用饿汉单例模式来保证程序在限制条件下的正常运行。

饿汉式单例模式虽然具有线程安全的优点,但在内存占用、类加载速度、扩展性和资源利用效率方面存在一些缺点。

  1. 类加载时初始化,占用空间:饿汉式单例模式在类加载时就完成了初始化,并创建了单例对象。这意味着无论是否需要使用该单例对象,都会占用一定的内存空间。如果单例对象的创建比较消耗系统资源,而外部一直没有调用该实例,那么这部分的系统资源消耗是没有意义的。

  2. 类加载较慢:由于饿汉式单例模式在类加载时就完成了初始化,这可能会导致类加载的速度相对较慢,尤其是在存在大量类需要加载的情况下。

  3. 扩展困难:饿汉式单例模式一般没有接口,扩展起来比较困难。如果要扩展单例对象的功能,只有修改代码这一途径,这不符合程序的开闭原则。

  4. 不支持延迟加载:饿汉式单例模式不支持延迟加载,即系统启动时就创建了单例对象,无论是否立即使用。这在某些情况下可能不是最优的资源利用方式。

懒汉式单例模式

懒汉式单例模式指的是在第一次使用时才创建单例对象。这种方式的实现相对复杂,需要注意线程安全问题,否则会导致多个线程同时创建多个对象。常见的解决方案是使用双重检查锁定。

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

上面的示例代码中,通过在 getInstance 方法中使用双重检查锁定来确保只有一个实例被创建。volatile 关键字可以避免指令重排序,以下是具体的说明。

instance = new Singleton()不是原子操作,这段代码可以简单分为下面三步执行:

  1.  为 instance 分配内存空间;

  2. 初始化 instance;

  3. 将 instance 指向分配的内存地址

由于但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2,造成未初始化完全的对象发布。

静态内部类单例模式

这种方式采用了类装载的机制来保证初始化实例时只有一个线程。类的静态属性只会在第一次加载类的时候初始化,在这里,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

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

将 instance 放在了内部类 SingletonHolder 中,前面我们提到饿汉式是类加载时就会立即创建对象,而静态内部类不会,它只会在调用了 getInstance 时,才会加载内部类 SingletonHolder,此时才会创建对象。利用静态内部类特点实现延迟加载,效率高。

枚举

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。

enum Singleton{INSTANCE;public void say(){System.out.println("hello");}
}

在上面的示例中,Singleton被定义为一个枚举,其中只有一个元素INSTANCE,该元素代表了Singleton的唯一实例。可以通过Singleton.INSTANCE来访问这个唯一的实例。

单例模式的使用场景

  1. 需要确保系统中某个类只有一个实例存在的情况。比如,一个配置管理类,一个日志记录器等。
  2. 需要对某个类的实例进行严格控制,以确保在系统中始终只有一个实例存在。例如,线程池、数据库连接池等。
  3. 需要避免创建大量的实例,以节约系统资源的情况。例如,一个类的实例需要占用大量的系统资源,如果创建多个实例可能会导致系统崩溃或者性能下降。

单例模式虽然能够保证某个类的实例只有一个,但也会带来一些缺点。例如,单例模式会增加代码的复杂度,可能会导致代码的可测试性降低,因此需要在使用单例模式时慎重考虑。

相关文章:

  • 工欲善其事必先利其器——IntelliJ IDEA神器使用技巧
  • Starlette
  • 盛夏之约,即将启程,2024中国北京消防展将于6月26举行
  • 数据结构(3)栈、队列、数组
  • 局域网电脑监控软件是如何监控到内网电脑的?
  • 【全开源】Java短剧系统微信小程序+H5+微信公众号+APP 源码
  • Docker面试整理-Docker的网络是如何工作的?
  • Keras深度学习框架实战(5):KerasNLP使用GPT2进行文本生成
  • 【2024年5月备考新增】】 考前篇(34)《必备资料(17) - 论文串讲-项目采购管理》
  • 单例模式(C语言)
  • B端数据看板,其实数据可以更美的。
  • 【人工智能】第六部分:ChatGPT的进一步发展和研究方向
  • 【C++ | 析构函数】类的析构函数详解
  • SQL语句练习每日5题(二)
  • JVM内存分析之JVM分区与介绍
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • Consul Config 使用Git做版本控制的实现
  • CSS盒模型深入
  • CSS相对定位
  • Hibernate【inverse和cascade属性】知识要点
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • JavaWeb(学习笔记二)
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • vue-loader 源码解析系列之 selector
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • 闭包--闭包之tab栏切换(四)
  • 从setTimeout-setInterval看JS线程
  • 分享一份非常强势的Android面试题
  • 高程读书笔记 第六章 面向对象程序设计
  • 如何设计一个微型分布式架构?
  • 数据结构java版之冒泡排序及优化
  • 一个SAP顾问在美国的这些年
  • 译有关态射的一切
  • 用jQuery怎么做到前后端分离
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • $.ajax()方法详解
  • (2.2w字)前端单元测试之Jest详解篇
  • (3) cmake编译多个cpp文件
  • (31)对象的克隆
  • (5)STL算法之复制
  • (C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切
  • (附源码)python房屋租赁管理系统 毕业设计 745613
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (接口封装)
  • (最完美)小米手机6X的Usb调试模式在哪里打开的流程
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .NET MAUI Sqlite程序应用-数据库配置(一)
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .net专家(高海东的专栏)
  • @GlobalLock注解作用与原理解析
  • @property python知乎_Python3基础之:property
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解