设计模式-创建型模式-单例模式

一、什么是单例模式

        单例模式,属于创建类型的一种常用的设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)。

        对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。(摘自百度百科)

        单例模式的应用场景有很多:

        1、数据库连接池的设计与实现;

        2、多线程的线程池设计与实现;

        3、Spring中创建的Bean实例默认都是单例;

        4、Java-Web中,一个Servlet类只有一个实例……

二、单例模式实现

2.1、饿汉模式

public class SingularDemo3 {private static SingularDemo3 instance = new SingularDemo3();private SingularDemo3() {System.out.println("SingularDemo3");}public static SingularDemo3 getInstance(){// 在程序启动的时候直接运行加载,后续有外部需要使用的时候获取即可// 导致的问题就像你下载个游戏软件,可能你游戏地图还没有打开呢,但是程序已经将这些地图全部实例化return instance;}
}

可以看到上述代码,很简单,直接在启动时加载,但是导致的就是,很多没用到的东西,他也给加载了,造成不必要的负担。

2.2、懒汉模式

2.2.1、使用synchronized锁

public class SingularDemo2 {public static volatile SingularDemo2 instance;private SingularDemo2() {  // 设置成私有,不允许外面调用System.out.println("SingularDemo2");}public static synchronized SingularDemo2 getInstance(){if(instance != null)    return instance;return new SingularDemo2();}
}

这种方式,是不用不加载,用到的时候再加载,使用synchronized加锁,保证只有一个线程访问,这种是线程安全的。但锁的粒度太大,导致竞争激烈,造成不必要的资源浪费。

同时这里的volatile,是防止指令重排序,导致导致对象未初始化完全。

2.2.2、使用双重校验锁

public class SingularDemo5 {public static volatile SingularDemo5 instance; // 要用volatile修饰,防止指令重排序private SingularDemo5() {System.out.println("singularDemo5");}public static SingularDemo5 getInstance(){if(instance != null)    return instance; // 如果有,直接返回synchronized (SingularDemo5.class){ // 锁住此对象if(instance != null)    return instance;  // 进入之后再判断一次instance = new SingularDemo5();return instance;}}
}

双重校验锁,降低了锁的粒度,只有当instance没有被初始化的时候,第一个访问的线程才回去实例化它。这里内部还要有一个判断instance是否为null,是为了防止同时多个线程竞争,一个线程初始化instance之后,其他线程也竞争到锁,再去初始化instance,所以进入临界区之后第一件事就是看看是否已经初始化好了。

这里的volatile有两个作用,一是保证可见性,其他线程读取的都是最新值;二是防止指令重排序导致对象未初始化完全。

2.2.3、类的内部类

public class SingularDemo4 {public static class Handle{public static SingularDemo4 instance = new SingularDemo4();}public SingularDemo4() {System.out.println("SingularDemo4");}public static SingularDemo4 getInstance(){return Handle.instance;}
}

类的内部类是靠JVM来保证并发的正确性,也就是一个类的构造方法在多线程下可以被正常的加载。我们永远可以相信JVM。

那问题来了?JVM是如何保证多线程下类的构造方法只会被执行一次的呢?

1、类加载机制和初始化锁

        当类加载器加载类时,JVM会使用类加载锁(Initialization Lock)来确保在同一时刻只有一个线程对类进行加载和初始化操作。

        一个线程开始初始化一个类时,其他线程需要等待这个类初始化完成,这是因为类的初始化过程中可能会执行静态代码块或静态变量赋值等操作,保证这些操作的互斥性能够避免多线程环境下的干扰。

2、Happens-Before

        在类的初始化过程中,对静态变量的赋值操作在对象引用发布之前必须完成,这意味着在静态变量赋值完成之前,其他线程不会看到不完整或部分初始化的对象。

2.2.4、使用CAS

public class SingularDemo7 {private static final AtomicReference<SingularDemo7> instance = new AtomicReference<>();private SingularDemo7(){System.out.println("singularDemo7");}public static SingularDemo7 getInstance() {while (true) {SingularDemo7 singularDemo7 = instance.get();if(singularDemo7 != null)   return singularDemo7;instance.compareAndSet(null, new SingularDemo7());return instance.get();}}
}

使用CAS方式保证单例,好处是不用加锁,效率更高;缺点也很明显,竞争激烈的情况下,可能大家都没法玩,一直循环,一直失败重试……

2.2.5、使用枚举类

public enum SingularDemo8 {instance;
}

Effective Java 作者推荐使用枚举的方式解决单例模式。

三、小结

        单例模式在实际的开发中应用的很多,它确保一个类只有一个实例,且这个实例的构造方法是私有的,并提供一个全局访问点来获取该实例。

        实现方式大致可以分为饿汉类和懒汉类,饿汉就是程序启动时就加载,这样有可能导致没有被用到的类也加载了,导致了性能的浪费;懒汉式的加载是用到就加载,不用不加载,但是要注意的是线程安全问题,合理的使用synchronized和volatile来保证线程安全。

        推荐使用的方式是双重校验锁和类的内部类,这两种方式既能保证懒加载,也能保证不会因为加锁或者锁的粒度较大导致的性能浪费。

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

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

相关文章

本地私域线上线下 线上和线下的小程序

私域商城是一种新型的零售模式&#xff0c;它将传统的线下实体店与线上渠道相结合&#xff0c;通过会员、营销、效率等方式&#xff0c;为消费者提供更加便利和高效的购物体验。私域商城的发展趋势表明&#xff0c;它将成为未来零售业的重要模式&#xff0c;引领零售业的创新和…

【设计模式】聊聊策略模式

策略模式的本质是为了消除if 、else代码&#xff0c;提供拓展点&#xff0c;对拓展开放&#xff0c;对修改关闭&#xff0c;也就是说我们开发一个功能的时候&#xff0c;要尽量的采用设计模式进行将不变的东西进行抽取出来&#xff0c;将变化的东西进行隔离开来&#xff0c;这样…

【开源】基于Vue.js的音乐偏好度推荐系统的设计和实现

项目编号&#xff1a; S 012 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S012&#xff0c;文末获取源码。} 项目编号&#xff1a;S012&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.1.1 音乐档案模块2.1…

【数据库】你听说过矢量数据库吗?

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️其他领域】 文章目录 前言什么是向量/矢量数据库嵌入模型使用向量数据库的优势与传统数据库的对比其他方面 AWS 如何支持您的矢量数据库需求&#xff1f;Amazon OpenSearch ServiceAmazon Aurora Pos…

[Docker]记一次使用jenkins将镜像文件推送到Harbor遇到的问题

系统版本&#xff1a; Ubuntu 18.01 私服&#xff1a; Harbor Docker版本&#xff1a; Docker version 18.09.5 首先需要明确的是&#xff0c;即在harbor里项目设置为公开&#xff0c;但是在push的时候还是需要用户验证的&#xff0c;即需要使用docker登录 docker login harbo…

CF1514 C. Product 1 Modulo N [妙妙题]

传送门:CF [前题提要]:感觉这道题是真的妙,解这道题的所有步骤都是一步一步按图索骥来的,有种玩解密游戏的感觉 题目很简单,就是求1~n中最长的子序列,使得这n个数的乘积模n为1. 乍一看很不好解决.那不妨先假设我们挑选了 k k k个数,然后这 k k k个数的乘积为 K K K, K K K模 …

【Unity】流体模拟(更新ing)

Fluid Simulation 参考于 Sebastian Lague 的项目进行分析学习 流体模拟视频链接 文章目录 Fluid Simulation2D流体Simulation2D.cs 2D流体 Simulation2D.cs 流体的边界用OnDrawGizmos设置流体的边界 void OnDrawGizmos(){Gizmos.color new Color(0, 1, 0, 0.4f);Gizmos.Dr…

vue中绑定class样式和条件渲染

绑定class样式 字符串写法&#xff1a; 适用于&#xff1a; 样式的类名不确定&#xff0c;需要动态指定 数组写法&#xff1a; 适用于&#xff1a; 要绑定的样式个数不确定&#xff0c;名字也不确定 绑定对象&#xff1a;适用于&#xff1a;要绑定的样式个数确定、名字确定、…

Python loglog()函数

常用坐标下的图像显示 import matplotlib.pyplot as plt import numpy as np import mathplt.figure() x_input np.linspace(1, 10, 50) y_input x_input**2plt.plot(x_input, y_input,r-,linewidth2) plt.show()在loglog函数尺度下的曲线 plt.loglog(x_input, y_input,r-,…

机器人走迷宫问题

题目 1.房间有XY的方格组成&#xff0c;例如下图为64的大小。每一个方格以坐标(x,y) 描述。 2.机器人固定从方格(0, 0)出发&#xff0c;只能向东或者向北前进&#xff0c;出口固定为房间的最东北角&#xff0c;如下图的 方格(5,3)。用例保证机器人可以从入口走到出口。 3.房间…

[JDK工具-2] javap 类文件解析工具-帮助理解class文件,了解Java编译器机制

文章目录 1. javap -version 版本信息2. javap -verbose 输出附加信息3. javap -l 显示行号和局部变量列表4. javap -c 对代码进行反汇编&#xff08;或叫反编译生成汇编代码&#xff0c;一般说反编译是生成java代码&#xff09;&#xff0c;分解方法代码&#xff0c;也就是显示…

想要成为CSS大师?这些技巧是你必须知道的!

前言 CSS 是网页设计中不可或缺的一部分&#xff0c;掌握一些实用的 CSS 技巧&#xff0c;可以让你在设计中展现出更多的创意和个性。本文将介绍一些 CSS 技巧&#xff0c;帮助你提升自己的技能&#xff0c;成为一个真正的 CSS 大师。 1. 改变 input 自动填充的背景颜色 这段 …

Ansible的dict的key里包含圆点.

环境 管理节点&#xff1a;CentOS Stream release 9控制节点&#xff1a;同上Ansible&#xff1a;2.15.4 从文件读取yaml数据 假设目标机器上有文件 data.yml 内容如下&#xff1a; a:b: 111c: 222d.e: 333现在要读取该文件内容&#xff0c;并转成yaml数据。 创建文件 tes…

高精度算法【Java】(待更新中~)

高进度加法 在Java中可以使用BigInteger进行高精度计算&#xff0c;除此也可以仿照竖式相加的计算原理进行计算。 BigInteger 提供所有 Java 的基本整数操作符的对应物&#xff0c;并提供 java.lang.Math 的所有相关方法。另外&#xff0c;BigInteger 还提供以下运算&#xff1…

UE5像素流送详细教程,以及解决黑边和鼠标消失问题

说明 本文是阅读UE5官方文档并且实践之后写的读书笔记,更多详细内容读者可以参考官方文档,本文只是因为在follow官方文档时发现有些操作界面不一样,有些细节遗漏了,所以才自己整理一篇笔记,以备后续使用。 读者按该文件指引操作后,可完成本地发布一个游戏,使用像素流网…

ts 联合react 实现ajax的封装,refreshtoken的功能

react ts混合双打&#xff0c;实现ajax的封装&#xff0c;以及401的特殊处理 import axios from axios import {AMDIN_EXPIRES_KEY,AMDIN_KEY,AMDIN_REFRESH_EXPIRES_KEY,AMDIN_REFRESH_KEY,COMMID_KEY,getToken,removeToken } from ../utils/user-token import { showMessage…

C#入门(7):接口详细介绍与代码演示

在C#中&#xff0c;接口是一种定义行为的契约。接口可以定义方法、属性、索引器和事件的签名&#xff0c;但它们都没有实现&#xff08;即&#xff0c;接口包含的都是抽象成员&#xff09;。任何实现了特定接口的类都需要提供接口定义的所有成员的具体实现。 C#接口的一些主要…

vscode运行dlv报错超时

描述 点击F5运行dlv调试go代码时报错&#xff1a;couldnt start dlv dap: connection timeout 解决方式 在网上搜索这个报错&#xff0c;据说是dlv的配置问题&#xff0c;修改配置后还是不行。有人说是dlv和go的版本不匹配&#xff0c;就朝这个方向试试 go版本改为1.19之后…

网上被吹爆的Spring Event事件订阅有缺陷,不要用

Spring Event事件订阅框架&#xff0c;被网上一些人快吹上天了&#xff0c;然而我们在新项目中引入后发现&#xff0c;这个框架缺陷很多&#xff0c;玩玩可以&#xff0c;千万不要再公司项目中使用。还不如自己手写一个监听者设计模式&#xff0c;那样更稳定、可靠。 之前我已…

C# params关键字

在C#中&#xff0c;params关键字用于指定一个方法参数&#xff0c;它可以接受任意数量的参数&#xff0c;或者说是一个参数数组。当使用params关键字时&#xff0c;你可以向方法传递逗号分隔的参数列表&#xff0c;或者是一个数组。在方法内部&#xff0c;这些参数被处理为一个…