【设计模式】-单例模式

简介

单例模式是一种创建型设计模式,确保某个类仅有一个实例,并提供一个全局访问点来访问该实例。
在单例模式中,类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,允许直接访问而无需每次实例化该类的新对象。

主要场景

单例模式的主要应用场景包括:

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。

通过应用单例模式,我们可以控制实例的数量,节省系统资源,同时加快对象访问速度,尤其适合对象需要被公用的场合,例如多个模块使用同一个数据源连接对象等。

实现机制

实现单例模式的关键在于确保只有一个实例被创建,并提供一个全局访问点。这通常通过以下方式实现:

  • 将构造函数设为私有,以防止其他类通过new操作符创建该类的实例。
  • 在类内部创建一个静态的私有实例。
  • 提供一个静态的公有方法,用于返回该类的唯一实例。如果该实例尚未创建,则通过调用私有构造函数来创建它;如果实例已经存在,则直接返回该实例。

注意多线程

需要注意的是,在多线程环境下,需要确保单例模式的线程安全性,以避免出现多个实例的情况。
单例模式确保线程安全的关键在于确保在多线程环境下,类的唯一实例能够被正确地创建和访问,而不会出现多个实例或者创建过程中的竞争条件。以下是一些常见的线程安全的单例模式实现方式:

饿汉式(静态初始化):

这种方式是最简单的线程安全的实现方式。因为静态初始化器由JVM在加载类时执行,且JVM保证类的加载过程是线程安全的,所以在多线程环境下,这种方式创建的实例也是线程安全的。

public class Singleton {// 在加载时就完成了初始化,所以类加载较慢,但获取对象的速度快private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}

双重检查锁定(Double-Checked Locking):

双重检查锁定是一种优化技术,旨在减少使用同步的开销。它首先检查实例是否已经存在,如果不存在才进行同步。但是,在Java中,双重检查锁定需要正确地使用volatile关键字来确保可见性和禁止指令重排

 public class Singleton {  // volatile 保证多线程正确处理 instance 的可见性和禁止指令重排  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 5及以上版本中,双重检查锁定是可行的,但务必注意volatile关键字的正确使用。

静态内部类:

静态内部类实现单例模式的方式也是线程安全的。由于JVM的类加载机制保证了静态内部类只会被加载一次,从而保证了线程安全性。

public class Singleton {private Singleton() {}// 使用静态内部类来创建单例,也是线程安全的private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

枚举:

在Java中,枚举类型是线程安全的,并且能防止反序列化重新创建新的对象,所以使用枚举实现单例模式也是线程安全的最佳实践。

public enum Singleton {INSTANCE;public void whateverMethod() {// method body}
}

通过枚举实现单例模式不仅简单,而且自动支持序列化机制,防止反序列化重新创建新的对象。

初始化时同步(Initialization on Demand Holder, IODH):

这种方式结合了饿汉式和双重检查锁定的思想,但在实现上更为复杂,且在某些JVM上可能并不是线程安全的。因此,它不如上面提到的方法常用。

在选择单例模式的实现方式时,应该考虑代码的简洁性、性能需求以及线程安全性。通常情况下,使用枚举或静态内部类实现单例模式是比较推荐的方式,因为它们不仅线程安全,而且实现简单,易于理解。

实际例子

日志系统

假设我们正在开发一个日志记录系统,我们想要确保整个应用程序中只有一个日志记录器实例,这样我们就可以保持日志的一致性和管理方便。

public class Logger {// 私有构造方法,防止外部通过new创建实例private Logger() {// 初始化代码System.out.println("Logger is being initialized...");}// 静态内部类,持有单例的引用private static class LoggerHolder {// 静态初始化器,保证线程安全private static final Logger INSTANCE = new Logger();}// 获取单例的公共静态方法public static Logger getInstance() {// 返回LoggerHolder中持有的单例引用return LoggerHolder.INSTANCE;}// 日志记录方法public void log(String message) {System.out.println("[" + System.currentTimeMillis() + "] " + message);}// 示例:使用单例模式的Logger类public static void main(String[] args) {// 获取Logger单例Logger logger = Logger.getInstance();// 使用Logger实例记录日志logger.log("This is a log message.");// 尝试再次获取Logger实例,应该是同一个Logger anotherLogger = Logger.getInstance();// 验证两个引用是否指向同一对象System.out.println("Are loggers the same? " + (logger == anotherLogger));}
}

数据库连接池

我们手撸一个最简单的数据库连接池,保证在并发情况下的单例访问。

import java.sql.Connection;  
import java.sql.DriverManager;  
import java.sql.SQLException;  
import java.util.concurrent.ConcurrentHashMap;  public class ConnectionPoolManager {  // 静态变量持有单例引用  private static ConnectionPoolManager instance;  // 数据库连接池  private final ConcurrentHashMap<String, Connection> pool;  // 初始化连接池的大小  private static final int MAX_CONNECTIONS = 10;  // 当前已分配的连接数  private int currentConnections = 0;  // 数据库连接信息  private static final String DB_URL = "jdbc:mysql://localhost:3306/mydb";  private static final String DB_USER = "username";  private static final String DB_PASSWORD = "password";  // 私有构造方法,防止外部通过new创建实例  private ConnectionPoolManager() {  this.pool = new ConcurrentHashMap<>();  // 初始化连接池,预先创建一些连接  for (int i = 0; i < MAX_CONNECTIONS; i++) {  try {  Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);  pool.put("connection" + i, connection);  } catch (SQLException e) {  e.printStackTrace();  }  }  }  // 从连接池中获取一个连接  public synchronized Connection getConnection() {  if (currentConnections < MAX_CONNECTIONS) {  // 尝试从连接池中获取一个连接  for (String key : pool.keySet()) {  Connection connection = pool.get(key);  if (connection != null && !connection.isClosed()) {  pool.remove(key); // 从连接池中移除,表示该连接已被使用  currentConnections++;  return connection;  }  }  // 如果连接池中没有可用连接,则创建新连接(此处简化处理,实际应检查是否超过最大连接数)  try {  Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);  currentConnections++;  return connection;  } catch (SQLException e) {  e.printStackTrace();  }  }  return null; // 如果没有可用连接,返回null  }  // 回收一个连接到连接池  public synchronized void releaseConnection(Connection connection) {  if (connection != null && !connection.isClosed()) {  pool.put("connection" + (MAX_CONNECTIONS - currentConnections), connection);  currentConnections--;  }  }  // 获取单例的公共静态方法,使用双重检查锁定确保线程安全  public static ConnectionPoolManager getInstance() {  if (instance == null) {  synchronized (ConnectionPoolManager.class) {  if (instance == null) {  instance = new ConnectionPoolManager();  }  }  }  return instance;  }  // 示例:使用单例模式的ConnectionPoolManager类  public static void main(String[] args) {  // 获取连接池管理器单例  ConnectionPoolManager connectionPoolManager = ConnectionPoolManager.getInstance();  // 从连接池获取一个连接  Connection connection = connectionPoolManager.getConnection();  if (connection != null) {  // 使用连接执行数据库操作...  System.out.println("Got a connection from the pool.");  // 假设使用完连接后释放回连接池  connectionPoolManager.releaseConnection(connection);  System.out.println("Released the connection back to the pool.");  } else {  System.out.println("No connections available in the pool.");  }  }  
}

在这个例子中,ConnectionPoolManager 类负责管理一个数据库连接池。它使用了 ConcurrentHashMap 来存储连接,以便能够高效地处理并发请求。getConnection 方法尝试从连接池中获取一个可用连接,如果没有可用连接则尝试创建一个新连接(这里简化了处理逻辑,实际中可能需要考虑连接数是否已经达到最大值)。releaseConnection 方法则将使用完毕的连接回收回连接池。

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

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

相关文章

【Linux】网络基础常识{OSI七层模型/ TCP/IP / 端口号 /各种协议}

文章目录 1.网络常识1.0DHCP协议1. 1IP地址/MAC地址/ARP协议是什么&#xff1f;IP/MACARP&#xff1a;IP ⇒ MAC 1.2手机连接wifi的原理 SSID与BSSID手机连接wifiSSID与BSSID 1.3手机如何通过“数据/流量”上网&#xff1f;1.4电脑连接wifi的原理&#xff1f;电脑通过热点上网…

C++ 11 的新特性

回答来自【通义灵码】 C11是C编程语言的一个重要里程碑&#xff0c;引入了大量的新特性以提升代码的可读性、可维护性、性能和安全性。以下是一些关键的C11新特性&#xff1a; 统一初始化: 允许在任何情况下使用花括号列表初始化&#xff08;uniform initialization&#xff0…

RH850P1X芯片学习笔记-Clocked Serial Interface H (CSIH)

文章目录 Features of RH850/P1x-C CSIHUnitsRegister Base AddressClock SupplyInterrupt RequestsHardware ResetExternal Input/Output Signals数据一致性检查 OverviewFunctional OverviewFunctional Overview DescriptionBlock Diagram RegistersList of RegistersCSIHnCT…

python怎么存储数据

在Python开发中&#xff0c;数据存储、读取是必不可少的环节&#xff0c;而且可以采用的存储方式也很多&#xff0c;常用的方法有json文件、csv文件、MySQL数据库、Redis数据库以及Mongdb数据库等。 1. json文件存储数据 json是一种轻量级的数据交换格式&#xff0c;采用完全…

【教学类-09-09】20240406细线迷宫图05(正方形)30格+动物+箭头(15CM横版一页-1份横版)

作品展示&#xff1a; 背景需求&#xff1a; 增加迷宫图的吸引力&#xff0c;起点的地方放一个小动物。 素材准备&#xff1a; 图片来自midjounery文生图&#xff08;四图&#xff09;&#xff0c;但同种动物只留1个&#xff08;如4个老鼠只保留一只老鼠&#xff09;&#xff…

哈希表2s总结

3.哈希表 哈希表非常常用&#xff0c;字典一般会用来保存处理过后的输入输出信息&#xff0c;集合也可以用来去重&#xff0c;这部分是重点&#xff0c;但是还是那句话&#xff0c;这种题目是不会或者说很少考原题的&#xff0c;主要还是学习知识&#xff0c;所以题目看一下答…

如何保证Redis的缓存和数据库中的数据的一致性?

Redis的缓存如何和数据库中的数据保持一致性&#xff1f; 我们都知道&#xff0c;Redis是一个基于内存的键值存储系统&#xff0c;数据完全存放在内存中&#xff0c;这使得它的读写速度远超传统的硬盘存储数据库。对于高访问频率、低修改率的数据&#xff0c;通过将它们缓存在…

python csv 工具包的使用

1. Introduction a. what’s csv CSV文件是一种纯文本文件&#xff0c;用于存储表格数据。CSV是Comma-Separated Values的缩写&#xff0c;意为逗号分隔值。每一行表示表格中的一条记录&#xff0c;而每个字段之间用逗号分隔。CSV文件可以用文本编辑器打开并编辑&#xff0c;…

intellij idea 使用git ,快速合并冲突

可以选择左边的远程分支上的代码&#xff0c;也可以选择右边的代码&#xff0c;而中间是合并的结果。 一个快速合并冲突的小技巧&#xff1a; 如果冲突比较多&#xff0c;想要快速合并冲突。也可以直接点击上图中 Apply non-conflicting changes 旁边的 All 。 这样 Idea 就会…

2024-04-04 问AI: 在深度学习中,微调是什么?

文心一言 在深度学习中&#xff0c;微调&#xff08;Fine-tuning&#xff09;是一种训练策略&#xff0c;特别针对预训练模型进行。预训练模型通常是在大规模无标注数据上通过自监督学习得到的&#xff0c;它们已经具备了对一般自然语言结构或视觉特征的良好理解能力。微调则是…

深入浅出 -- 系统架构之垂直架构

当业务复杂度增加、访问量逐渐增大出现高并发时&#xff0c;单体架构无法满足需求&#xff0c;可以根据业务功能对系统进行拆分&#xff0c;以提高访问效率。 垂直架构介绍 1.垂直架构一般是因为单体架构太过于庞大而进行的拆分&#xff0c;拆分后各个系统应满足独立运行互相不…

wordpress外贸独立站模板

wordpress外贸独立站模板 WordPress Direct Trade 外贸网站模板&#xff0c;适合做跨境电商的外贸公司官方网站使用。 https://www.waimaoyes.com/wangzhan/22.html

【Android Studio】上位机-安卓系统手机-蓝牙调试助手

【Android Studio】上位机-安卓系统手机-蓝牙调试助手 文章目录 前言AS官网一、手机配置二、移植工程三、配置四、BUG五、Java语言总结 前言 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 AS官网 AS官网 一、手机配置 Android Studio 下真机调试 …

算法| ss 二分

34.在排序数组中查找元素的第一个和最后一个位置35.搜索插入69.x的平方根875.爱吃香蕉的珂珂 34.在排序数组中查找元素的第一个和最后一个位置 /*** param {number[]} nums* param {number} target* return {number[]}*/ // 思路 // 新建一个search函数&#xff0c;参数为是否…

unity学习(82)——profiler 限制帧率

实际测试发现当玩家个数增加时&#xff0c;客户端明显变的很卡&#xff0c;想知道为什么变卡了&#xff01; 1.只有玩家自己的时候 2.两个时候感觉脚本的工作量增大了 拖了一会直接炸了&#xff01;&#xff08;数据包积压把内存搞炸&#xff0c;我第一次见&#xff09; 3.我觉…

数据库的介绍分类作用特点

目录 1.概述 2.分类 2.1.关系型数据库 2.2.非关系型数据库 2.3.分布式数据库 ​​​​​​​2.4.云数据库 3.作用 4.特点 5.应用举例 5.1.MySQL ​​​​​​​5.1.1.作用 ​​​​​​​5.1.2.特点 ​​​​​​​5.1.3.应用案例 ​​​​​​​5.2.达梦 ​​​…

上位机图像处理和嵌入式模块部署(qmacvisual之tcp服务器端)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 上面一篇&#xff0c;我们谈到了tcp客户端&#xff0c;另外一种连接方法就是tcp服务器端。事实上&#xff0c;对于第三方系统&#xff0c;大多数情…

Python进阶:使用requests库轻松发送HTTP请求并获取响应

Python进阶&#xff1a;使用requests库轻松发送HTTP请求并获取响应 简介&#xff1a;本文将带您深入了解Python中强大的requests库&#xff0c;学会如何使用它发送各种HTTP请求&#xff0c;并轻松获取响应内容。无论您是初学者还是有一定经验的Python开发者&#xff0c;本文都…

ES10 学习

文章目录 1. Object.fromEntries()2. trimStart() 和 trimEnd()3. 数组的flat() 和flatMap()4. Symbol 对象的description 属性5. try ... catch(e){} 1. Object.fromEntries() Object.fromEntries() 方法允许你轻松地将键 值对列表转换为对象 let arr [["name",&qu…

《搜广推算法指南》(2024版) 重磅发布!

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 结合…