单例模式的实现

当谈及单例模式时,我们指的是一种设计模式,它确保某个类只有一个实例,并提供一个全局访问点。在Java中,单例模式是最常用的设计模式之一,它可以确保一个类在应用程序的生命周期内只有一个实例,并提供全局访问点以便访问该实例。

1. 为什么要使用单例模式?

单例模式主要用于以下几种情况:

  1. 当一个类只能有一个实例,且客户端需要访问这个实例时,可以使用单例模式。
  2. 当希望避免由于创建太多对象导致的性能问题时,可以使用单例模式。
  3. 当需要控制资源的访问权限时,可以使用单例模式。

2. 实现单例模式的方式

在Java中,有多种方式可以实现单例模式,下面我们将介绍其中比较常见的三种实现方式:懒汉式、饿汉式和双重检查锁定。

2.1 饿汉模式

饿汉模式是指在类加载时就创建实例,由于这个实例创建的非常早,所以使用饿汉描述,形容非常迫切。这种方式在多线程环境下也能保证单例的唯一性。

 示例代码:

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

在上述代码中我们可以看到,我们在类属性中实例化了一个该类的对象,然后给出了一个获取这个对象的方法,然后我们把默认的无参构造方法实现为 “私有的”,让外界无法直接实例化新的对象。

2.2懒汉模式

懒汉式单例是指在需要时才创建实例。当第一次调用获取实例的方法时,才会创建实例对象。

示例代码:

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

如果首次调用getInstance() ,此时instance为null,就会进入if条件,从而创建实例,等再次调用getInstance则不会再创建实例。

但是上如代码如果在多线程中调用,是有可能创建多个实例的:

假设有两个线程 t1, 和 t2 都在调用 getInstance ,当 t1 刚好执行到进入 if 内部 还没有 把创建好的实例赋值给 instance 的时候,被调度除了cpu,此时,instance 仍然等于 null, t2线程就有可能进入 if 内从而再次创建一个实例。

如何改进,让上述代码变为线程安全的代码?

上诉代码线程不安全的原因是,if 和 new 操作之间可能再次运行了其他线程的 if 和 new 操作,于是我们可以通过加锁,把这两个操作打包在一起:

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

上述代码就保证了 if 和 new 操作之间 不会执行其他线程的 if 和 new 操作。但是我们发现上述代码,其实只有在第一次调用的时候才需要加锁,后续都直接返回 instance 即可 ,但是每次仍然都是先加锁再解锁,这样会导致该代码的效率会变低,于是我们可以在该方法的上面再加一个 if 判断:

class SingletonLazy {private static SingletonLazy instance = null;private SingletonLazy() {}private static Object locker = new Object();public static SingletonLazy getInstance() {if(instance == null) {//如果 instance 为 null 说明是首次调用,需要加锁synchronized (locker) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}
}

注意上述代码中两个 if 的结果是可能不同的。第一个 if 和 第二个 if 之间可能 会执行其他 线程调用 的 getInstance 导致 instance 不为 null。

上述代码 虽然保证了 if 和 new 操作之间 不会执行其他线程的 if 和 new 操作,但仍可能存在线程安全问题。

指令重排序引起的线程安全问题:

指令重排序是指处理器在执行指令时可能会改变指令的顺序,以提高程序的运行效率。

我们来看这行代码:

instance = new SingletonLazy();

这行代码可以拆分为三个步骤(不是三个cpu指令):

  1. 申请一段内存空间
  2. 在这个内存上创建出实例
  3. 把这段空间的内存地址赋值给 instance 

正常情况下,上述步骤是按 1 2 3 的顺序来执行的,但是编译器也可能优化成 1 3 2来执行,
现在假设有 t1 , t2 两个线程 ,如果此时,t1 正在以 1 3 2,的顺序在执行这行代码,t1在刚执行了  1 和 3 之后 ,就被调度出了cpu ,此时,instance 的值已经不为空,但是指向的内存中却还没有创建 实例,此时 t2 线程又刚好开始执行 getInstance ,于是,t2 线程就得到了,一块没有被初始化的内存空间,此时如果,t2 再尝试使用这块内存空间中的内容,就会引发错误 此时的instance是一个“全0”的值。

所以我们 应该给 instance 加上 volatile 关键字, 来禁止指令重排序

class SingletonLazy {private volatile static SingletonLazy instance = null;private SingletonLazy() {}private static Object locker = new Object();public static SingletonLazy getInstance() {if(instance == null) {//如果 instance 为 null 说明是首次调用,需要加锁synchronized (locker) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}
}

 现在上述代码就是一个线程安全的代码了

2.3 懒汉模式和饿汉模式的优缺点

懒汉式单例模式: 优点:

  1. 延迟加载:只有在首次调用getInstance()方法时才会创建实例,可以节约资源。
  2. 线程安全(双重检查锁定):通过使用synchronized关键字和双重检查锁定机制,可以保证多线程环境下的线程安全性。

缺点:

  1. 可能存在线程安全问题:尽管在代码中通过使用synchronized关键字和双重检查锁定机制来解决线程安全性问题,但在某些特定情况下可能会发生失效的情况,例如编译器优化问题。
  2. 实现相对复杂:双重检查锁定机制的实现相对复杂,容易出错,并且对于不熟悉该机制的开发人员来说,难以理解和维护。

饿汉式单例模式: 优点:

  1. 简单直观:实现起来简单,不存在线程安全问题。
  2. 线程安全:由于实例在类加载时就创建,因此不存在多线程环境下的线程安全问题。

缺点:

  1. 资源浪费:在类加载时就创建实例对象,可能会导致资源的浪费,特别是在实例的创建过程中需要执行耗时操作或占用大量资源的情况下。
  2. 无法实现延迟加载:由于实例在类加载时就被创建,无法实现按需创建实例的延迟加载需求。

总结: 懒汉式单例模式具有延迟加载和线程安全的优点,但也存在一些线程安全问题和实现复杂的缺点。饿汉式单例模式简单直观,线程安全,但可能存在资源浪费和无法实现延迟加载的缺点。在选择单例模式实现方式时,需要根据具体需求和场景进行权衡和选择。

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

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

相关文章

【文献阅读】Joint Demosaicing and Denoising with Self Guidance

1. 摘要 近年来,一些神经网络在联合去马赛克和去噪(JDD)方面表现出了良好的效果。大多数算法首先将Bayer原始图像分解为四通道RGGB图像,然后将其输入神经网络。这种做法忽略了一个事实,即绿色通道的采样率是红色和蓝色通道的两倍。在本文中&…

自定义注解验证数据字典选项及bean注入问题

我们在工作中经常需要对字典选项进行定义,如果客户端传来的字典项不符合要求,那么根本无法保存,但是已有的注解并没有字典值的验证,那我们就自己实现一个 一、自定义字典值验证的注解DictValid import javax.validation.Constra…

pycharm 创建vue并实现简易路由功能

使用pycharm创建vue项目时,选择vite来创建vue。为什么使用vite?因为vite是专门针对vue开发的打包框架,以前使用vue-cli来创建vue项目,就是使用的webpack来进行打包的,现在有了vite,就尽量使用vite来创建vue…

Amazon CodeWhisperer 正式发布可免费供个人使用

文章作者:sunny 亚马逊云科技日前推出了实时 AI 编程助手 Amazon CodeWhisperer,包括个人套餐和专业套餐,所有开发人员均可免费使用个人套餐。Amazon CodeWhisperer 让开发人员能够保持专注、高效,帮助他们快速、安全地编写代码&a…

备战春招——12.3 算法

哈希表 哈希表主要是使用 map、unordered_map、set、unorerdered_set、multi_,完成映射操作,主要是相应的函数。map和set是有序的,使用的是树的形式,unordered_map和unordered_set使用的是散列比表的,无序。 相应函数…

RabbitMQ 消息中间件 消息队列

RabbitMQ1、RabbitMQ简介 RabbiMQ是⽤Erang开发的,集群⾮常⽅便,因为Erlang天⽣就是⼀⻔分布式语⾔,但其本身并不⽀持负载均衡。支持高并发,支持可扩展。支持AJAX,持久化,用于在分布式系统中存储转发消息&a…

福德植保无人机案例:无人机种地的那些事儿

大家好,今天我要给大家介绍一个非常有趣的案例,那就是我们的福德植保无人机工厂。这个工厂可不简单,它可是无人机植保领域的佼佼者,让我们一起来看看他们的故事吧!首先,让我们来了解一下无人机植保这个概念…

简谈MySQL的binlog模式

一、MySQL的binlog模式介绍 MySQL的binlog模式是一种日志模式,用于记录对MySQL数据库进行的更改操作。通过启用binlog模式,可以将数据库的更改操作记录到二进制日志文件中,以便在后续需要时进行恢复和复制。 要启用binlog模式,请…

ROS-ROS通信机制-话题通信

文章目录 一、话题通信基础知识二、话题通信基本操作2-1 C2-2 Python2-3 C与python节点通信 三、自定义msg3-1 自定义msg3-2 C实现自定义msg调用3-3 Python实现自定义msg调用 一、话题通信基础知识 话题通信实现模型是比较复杂的,该模型如下图所示,该模型中涉及到三…

Kubernetes(K8s) Ingress介绍-08

Ingress介绍 在前面课程中已经提到,Service对集群之外暴露服务的主要方式有两种:NotePort和LoadBalancer,但是这两种方式,都有一定的缺点: NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务…

中级工程师评审条件:如何成为一名合格的中级工程师

作为一名工程师,不仅需要具备扎实的技术基础和实践能力,还需要通过评审来证明自己的能力水平。在成为一名合格的中级工程师之前,你需要满足一系列评审条件。甘建二今天将详细介绍中级工程师评审的要求和标准,帮助你成为更优秀的工…

树_左叶子之和

//给定二叉树的根节点 root ,返回所有左叶子之和。 // // // // 示例 1: // // // // //输入: root [3,9,20,null,null,15,7] //输出: 24 //解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24 //…

geoserver维度time

postgis创建date类型的字段 写入测试数据,对应flag,flag有不同的样式,这样方便观测 geoserver发布图层的时候设置“维度”启用 测试,设置了根据flag展示不同的颜色

Cisco思科 路由交换网络设备基线安全加固操作

目录 账号管理、认证授权 本机认证和授权ELK-Cisco-01-01-01 设置特权口令 ELK-Cisco-01-02-01 ​​​​​​​ELK-Cisco-01-02-02 ​​​​​​​登录要求 ELK-Cisco-01-03-01 ​​​​​​​ELK-Cisco-01-03-02 ELK-Cisco-01-03-03 日志配置 ELK-Cisco-02-01-01 通信协…

在CentOS7下安装Docker与Docker Compose

目录 Docker简介 Docker安装 Docker Compose简介 Docker Compose安装 Docker简介 Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的Linux或Windows操作系统的机器上,也…

外卖平台推荐算法的优化与实践

目录 引言 一、推荐算法的原理 二、推荐算法的挑战 三、实际案例分析 四、优化推荐算法的策略 五、结论 引言 在当今数字化社会,外卖平台成为了人们生活中不可或缺的一部分。为了提供更加个性化、高效的服务,外卖平台使用推荐算法成为了一项关键技…

深度学习算法:探索人工智能的前沿

目录 引言 第一部分:深度学习的基础 1.1 什么是深度学习? 1.2 神经网络的演化 第二部分:深度学习的关键技术 2.1 卷积神经网络(CNN) 2.2 循环神经网络(RNN) 2.3 长短时记忆网络&#xf…

屈臣氏集团

屈臣氏集团(英语:A.S. Watson Group),简称屈臣氏(A.S. Watson、Watson、ASW)[注 1],以屈臣氏集团(香港)有限公司(A.S. Watson Group (Hong Kong) Ltd.&#x…

python自学之《艾伯特用Python做科学计算》(1)——(待完善)

好吧,刚开始就打了一波而广告 啄木鸟社区的Python图书概览: http://wiki.woodpecker.org.cn/moin/PyBooks (22/388)

ASP.NET 网上选课系统的设计与实现

1 系统设计与实现 1.1 数据库设计 为充分保护数据的一致性,数据库中各表都规范化设计,下图是系统数据库中使用的表以及各表之间的关系: 下面就各个表分别给出说明: (1)课程基本信息(CourseInfo)表&#x…