JavaEE——单例模式

news2024/11/27 10:39:28

文章目录

  • 一、介绍什么是单例模式
  • 二、饿汉模式
  • 三、懒汉模式
  • 四、讨论两种模式的线程安全问题

一、介绍什么是单例模式

在介绍单例模式之前,我们得先明确一个名词设计模式

所谓设计模式其实不难理解,就是在计算机这个圈子中,呢些大佬们为了防止我们这些资质平平的程序猿不把代码写的太差,所设计出的针对一些场景的问题解决方案,解决框架。

单例模式就是多个设计模式中的一个。

单例模式,逐字来理解就是:单个示例对象的意思。
在某些场景中,有些特定的类只能创建出一个实例,不应该创建多个实例。这样的要求虽然可以通过程序员本人进行控制,但是仍然存在不确定性。
使用单例模式后,此时只能创建 1 个 实例,单例模式就是针对上述的特殊需求的一个强制的保证。

在 Java 中实现单例模式的方法有很多,这里介绍一下最常见的两类。
(1) 饿汉模式
(2) 懒汉模式

二、饿汉模式

我们已经知道,单例模式就是要让某个类创建出唯一一个对象,所谓饿汉,字面理解就是一个饿了很久的人,这样的人在看到吃的就会异常急切。
这里的饿汉模式,就是让代码在一开始执行的时候,即就是类加载阶段,就去创建这个类的实例,这种效果就给人一种 “非常急切” 的感觉。
下面我来展示一下相关的代码示例

//饿汉模式
//保证 Singleton 这个类只能创建一个实例
class Singleton{
    //在此处先将实例创建出来
    private static Singleton instance = new Singleton();

    //如果要使用这个唯一实例,同意通过 Singleton.getInstance() 方式获取
    public static Singleton getInstance(){
        return instance;
    }

    //为了避免 Singleton 类被复制多份
    //将构造方法设置为 private。再类外面无法通过 new 来实现创建 Singleton 实例
    private Singleton(){}
}

public class ThreadDemo {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        System.out.println(s1 == s2);
    }
}

在这里插入图片描述
上面的结果就表明这是同一个实例。

static 在这里的作用(了解 static 关键字作用的可以跳过)

在这里插入图片描述
static 在这里保证了使用的对象是唯一的,下面通过代码示例来解释一下:

class Test{
    public int A;
    public static int B;
}

public class StaticTest {
    public static void main(String[] args) {
        Test t1 = new Test();
        Test t2 = new Test();
        //设置非 static 修饰的变量
        t1.A = 10;
        t2.A = 20;
        System.out.println("t1.A的值"+ t1.A);
        System.out.println("t2.A的值"+ t2.A);

        //设置由 static 修饰的变量
        t1.B = 10;
        t2.B = 20;
        System.out.println("t1.B的值"+ t1.B);
        System.out.println("t2.B的值"+ t1.B);
    }
}

在这里插入图片描述总的来说,被 static 修饰的关键字在代码中表现的内容与最后一次设定是一样的,这样也就确保了使用对象的唯一性。

三、懒汉模式

对于懒汉模式 “懒汉” 字面意义上不难理解,就是表示一个人很懒,直到事情迫在眉睫才会去做。
在这里,懒汉模式也是同样,在类加载之初是不会进行对象的创建,一直到第一次真正使用的时候才会去创建

代码示例:

//实现懒汉模式
class SingletonLazy{
    //这里先将对象不进行创建,先设定为 null
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        //判断是否有对象被创建,没有就进行创建
        if (instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
    //将构造方法设定为 static 不能进行 new 来创建
    private SingletonLazy(){}
}

public class ThreadDemo21 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

运行结果
在这里插入图片描述
同样的,这一样表示了上面运用的是同一个对象。

四、讨论两种模式的线程安全问题

上面的 饿汉模式 和 懒汉模式,在多线程的调用下是很有可能存在线程安全问题的。下面我来简单分析一下。

前面我们已经了解了关于线程安全问题的部分知识,我们知道,计算机中对数据的处理分为,1. 从内存 “读”。2.在cpu寄存器上 “改”。3. “写”入内存。这几个操作。在这里我们就根据上面几点来进行解释。

在这里插入图片描述
在这里插入图片描述
如上图所示,我们发现饿汉模式只存在这个操作,操作很单一,在这里就没有线程安全问题。懒汉模式存在着读和写两个操作,对此如果不进行约束,是有可能会出现线程安全问题。如下图所示:

在这里插入图片描述
呢么,如何才能让懒汉模式 线程安全?答案是:加锁。

class SingletonLazy{
    //这里先将对象不进行创建,先设定为 null
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        //通过加锁消除线程安全问题,在此处加锁才能保证读操作和修改操作是一个整体
        synchronized (SingletonLazy.class){
            //判断是否有对象被创建,没有就进行创建
            if (instance == null){
                instance = new SingletonLazy();
            }    
        }
        return instance;
    }
    //将构造方法设定为 static 不能进行 new 来创建
    private SingletonLazy(){}
}

这里加锁之后,t1 线程已经创建了一个对象,之后的 t2 线程 load 到的结果是前面线程修改的结果。因此 t2 线程就不会在创建新的对象,直接返回现有的对象。

需要注意的是,在这里的加锁操作,在线程每进行 getInstance 操作时每次都会进行加锁,但是每次的加锁都要有开销,真的需要每次加锁吗?我们知道在创建对象前,需要进行加锁,但是,在第一次创建对象后,后续的操作会直接触发 return,对此可以对代码进行如下修改:

//实现懒汉模式
class SingletonLazy{
    //这里先将对象不进行创建,先设定为 null
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance(){
        //负责判断是否要加锁
        if(instance == null){
            //通过加锁消除线程安全问题,在此处加锁才能保证读操作和修改操作是一个整体
            synchronized (SingletonLazy.class){
                //判断是否有对象被创建,没有就进行创建
                if (instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    //将构造方法设定为 static 不能进行 new 来创建
    private SingletonLazy(){}
}

注:上面的两个 if 条件语句虽然条件相同,但是两者之间控制的问题完全不同。

有关懒汉模式上面的加锁操作以及对加锁操作的修改仍然没有完全解决其中存在的问题。这里还存在内存可见性问题,指令重排序问题。

内存可见性问题: 在这里,假设有很多线程都去进行 getInstance,此时就很有可能有被优化的风险,只有第一次读取了真正的内存,后续读取的都是寄存器中的数据。

指令重排序问题: 在这里,我们将 instance = new SingIeton(); 拆分为下面三部分。

  • 1.申请内存空间
  • 2.调用构造方法,将内存空间初始化为一个合理的对象
  • 3.将内存地址赋值给 instance 引用。

在正常情况下,按照1,2,3这样的顺序进行执行的。但是,在多线程的环境下,就会出现问题。

假设线程 t1 是在排序后按照 1,3,2这个顺序进行执行的。当执行完 1,3 这两个步骤后,被切出 cpu ,此时 t2 进入cpu,这里就出现问题了,这里的 t2 拿到的是非法的对象,是没有构造完成的不完整对象。

要解决上面的问题 volatile关键字可以解决问题。
volatile 关键字有下面两个功能:
(1) 解决内存可见性
(2) 禁止指令重排序

代码如下:

//实现懒汉模式
class SingletonLazy{
    //这里先将对象不进行创建,先设定为 null
    //添加 volatile 关键字,防止指令重排序,解决内存可见性问题
    private volatile static SingletonLazy instance = null;
    
    public static SingletonLazy getInstance(){
        //负责判断是否要加锁
        if(instance == null){
            //通过加锁消除线程安全问题,在此处加锁才能保证读操作和修改操作是一个整体
            synchronized (SingletonLazy.class){
                //判断是否有对象被创建,没有就进行创建
                if (instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    //将构造方法设定为 static 不能进行 new 来创建
    private SingletonLazy(){}
}

到此,关于懒汉模式的相关问题基本解决。

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

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

相关文章

驾考系统C#winform驾照考试系统

驾考系统C#winform驾照考试系统 c#,sqlite,winform ,.net framwork4.0驾照考试系统 有兴趣的朋友可以修改源代码玩玩!我用的数据库是sqlite (随着我国社会的不断进步和发展,越来越多的家庭拥有汽车,人们…

[linux-sd-webui]api之dreambooth训练

https://gitee.com/leeguandong/dreambooth-for-diffusionhttps://gitee.com/leeguandong/dreambooth-for-diffusionhttps://zhuanlan.zhihu.com/p/584736850https://zhuanlan.zhihu.com/p/584736850这个库使用的是diffusers库,现在主要就是kohya-ss/sd-scripts混合…

模拟退火算法与遗传算法求解多目标优化问题的算法实现(数学建模)

一、模拟退火算法 模拟退火算法是一种全局优化算法,解决的问题通常是找到一个最小化(或最大化)某个函数的全局最优解。它通过模拟物理退火的过程来搜索解空间,在开始时以一定的温度随机生成初始解,然后一步步降低温度…

java 图形化小工具Abstract Window Toolit :画笔Graphics,画布Canvas(),弹球小游戏

画笔Graphics Java中提供了Graphics类,他是一个抽象的画笔,可以在Canvas组件(画布)上绘制丰富多彩的几何图和位图。 Graphics常用的画图方法如下: drawLine(): 绘制直线drawString(): 绘制字符串drawRect(): 绘制矩形drawRoundRect(): 绘制…

YOLOv8——CV界的XGBoost

yolov8是ultralytics公司于2023年1月开源的anchor-free的最新目标检测算法框架。 封装在ultralytics这个库中:https://github.com/ultralytics/ultralytics 它具有以下优点: 1,性能速度领先:借鉴了之前许多YOLO版本的trick&#x…

spring常用的事务传播行为

事务传播行为介绍 Spring中的7个事务传播行为: 事务行为 说明 PROPAGATION_REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务 PROPAGATION_SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行 PROPAGATION_MANDATORY…

ChatGPT能胜任高级程序员吗?

与开发人员信任的其他软件开发工具不同,AI工具在训练、构建、托管和使用方式等方面都存在一些独特的风险。 自2022年底ChatGPT发布以来,互联网上便充斥着对其几乎相同比例的支持和怀疑的论调。不管你是否喜欢它,AI正在逐步进入你的开发组织。…

JAVA ssm客户信息管理系统idea开发mysql数据库web结构计算机java编程springMVC

一、源码特点 idea ssm客户信息管理系统是一套完善的web设计系统mysql数据库springMVC框架mybatis,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开 发。 java ssm客户信息管理系统idea开发mysql数据…

【五一创作】 SAAS-HRM系统概述与搭建环境

SAAS-HRM系统概述与搭建环境 学习目标: 理解SaaS的基本概念 了解SAAS-HRM的基本需求和开发方式掌握Power Designer的用例图 完成SAAS-HRM父模块及公共模块的环境搭建完成企业微服务中企业CRUD功能 初识SaaS 云服务的三种模式 IaaS(基础设施即服务…

业务维度digest日志的记录与监控方案

需求 ​   为了满足从业务整体的维度 实现监控和链路复原,我们希望对于一个业务接口,记录一行请求日志,并通过某个 Unique Id(如UserId、OrderId)将多行日志关联起来,最终产出一批和业务强相关的数据&am…

软件维护(Software maintenance)的流程

软件维护(Software maintenance)是一个软件工程名词,是指在软件产品发布后,因修正错误、提升性能或其他属性而进行的软件修改。 软件维护主要根据需求变化或硬件环境的变化对应用程序进行部分或全部的修改,修改时应充分利用源程序。修改后要填…

2023年的深度学习入门指南(10) - 前端同学如何进行chatgpt开发

2023年的深度学习入门指南(10) - 前端同学如何进行chatgpt开发 在第二篇,我们使用openai的python库封装,搞得它有点像之前学习的PyTorch一样的库。这一节我们专门给它正下名,前端就是字面意义上的前端。 给gpt4写前端 下面我们写一个最土的…

【BeautifulSoup】——05全栈开发——如桃花来

目录索引 介绍:解析库: 安装:pip install BeautifulSoup4pip install lxml 标签选择器:1.string属性:.name属性:获取标签中的属性值: 实用——标准选择器:使用find_all()根据标签名查…

百城巡展 | 人大金仓4月“双向奔赴”告一段落

人间最美四月天,人大金仓走过上海、宁波、合肥,联合伙伴发布医疗、金融、信息安全、电子档案等多个关键领域的信创联合解决方案,共同为数字基础设施的安全和可持续发展贡献力量,吸引了线上线下近7000人参与。 左右滑动&#xff0c…

大数据架构(一)背景和概念

-系列目录- 大数据架构(一)背景和概念 大数据架构(二)大数据发展史 一、背景 1.岗位现状 大数据在一线互联网已经爆发了好多年,2015年-2020年(国内互联网爆发期)那时候的大数据开发,刚毕业能写Hive SQL配置个离线任务、整个帆软报表都20K起步。如果做到架…

Midjourney 创建私人画图机器人,共享账号如何设置独立绘画服务器(保姆级教程)

你是不是遇到以下问题: 1.Midjourney会员怎么自建绘图服务器,不受其他人的打扰? 2.Midjourney会员共享账号如何自建服务器,供其他人使用? 3.在官方服务器作图,频道里面的人太多了,自己的指令…

【五一创作】( 字符串) 409. 最长回文串 ——【Leetcode每日一题】

❓ 409. 最长回文串 难度:简单 给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串 。 在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。 示例 1: 输入:s “abccccdd”…

时序预测 | Matlab实现SSA-GRU、GRU麻雀算法优化门控循环单元时间序列预测(含优化前后对比)

时序预测 | Matlab实现SSA-GRU、GRU麻雀算法优化门控循环单元时间序列预测(含优化前后对比) 目录 时序预测 | Matlab实现SSA-GRU、GRU麻雀算法优化门控循环单元时间序列预测(含优化前后对比)预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现SSA-GRU、GRU麻雀算法…

第十四章 移动和旋转(下)

本章节我们介绍另外两种形式的旋转,也对应了两个方法。首先是RotateAround方法,他是围绕穿过世界坐标中的 point 点的 axis轴旋转 angle 度。这个方法虽然比较晦涩难懂,但是我们使用一个案例,大家就非常明白了。我们创建一个新的“…

JDBC详解(三):使用PreparedStatement实现CRUD操作(超详解)

JDBC详解(三):使用PreparedStatement实现CRUD操作(超详解) 前言一、操作和访问数据库二、使用Statement操作数据表的弊端三、PreparedStatement的使用1、PreparedStatement介绍2、PreparedStatement vs Statement3、Ja…