【JavaEE】多线程案例-单例模式

2023-11-14

在这里插入图片描述

1. 前言

单例模式是我们面试中最常考到的设计模式。什么是设计模式呢?

设计模式是在计算机科学中,对面向对象设计中反复出现的问题的解决方案的描述。它是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

设计模式的目的在于可重用代码、让代码更容易被他人理解、提高代码的可靠性。它们通常描述了一组相互紧密作用的类与对象,提供了讨论软件设计的公共语言,使得熟练设计者的设计经验可以被初学者和其他设计者掌握。此外,设计模式还为软件重构提供了目标。

设计模式可以根据目的分为以下三类:

  1. 创建型模式:主要用于创建对象,这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。
  2. 结构型模式:主要用于处理类和对象的组合。
  3. 行为型模式:主要用于描述类或对象如何交互和怎样分配职责。

此外,根据范围,即模式主要是处理类之间的关系还是处理对象之间的关系,可分为类模式和对象模式两种。

2. 什么是单例模式

单例模式保证一个类在程序中只存咋一个实例,而不会创建出多个实例。就像一个人只能有一个伴侣,而不能有多个伴侣一样。

3. 如何实现单例模式

虽然我们可以自己人为的控制该类只存在一个实例,但是我们人是最不能相信的生物,所以就需要使用计算机来对我们进行约束。当我们想要创建多个实例的时候,就需要编译器做出相应的反应:抛异常或者直接结束进程等。

在Java中实现单例模式可以有两种方式:

  1. 饿汉模式
  2. 懒汉模式

3.1 饿汉模式

要想保证某个类只存在一个实例,其中一个很好的方法就是我们在定义这个类的时候就创建一个实例,并且这个实例是唯一的,当出了这个类的时候就不允许再创建该类的实例了。

class Singleton {
	//定义类的时候就创建一个唯一的实例
    private static Singleton instance = new Singleton();
    
    public static Singleton getInstance() {
        return instance;
    }
}

因为出了这个类之后不能再创建该类的实例,并且我们需要获得在该类定义时创建的实例,所以可以使用一个静态的 getInstance 方法来获得这个唯一的实例。

虽然我们创建出了这个唯一的实例,但是应该怎样保证出了这个类之后不能再创建实例了呢?

我们都知道,每次创建一个实例的时候,都会调用该类的构造方法(如果你没有实现构造方法,编译器会为你默认创建一个无参数的构造方法),所以我们可以从这个构造方法入手:将构造方法改为私有的构造方法,只有在这个类中创建实例的时候才会创建成功,出了这个类之后,如果再创建第二个实例的时候,因为构造方法是私有的,所以就会创建失败。

class Singleton {
    private static Singleton instance = new Singleton();

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

当我们想要创建多个实例的时候,看看会发生什么情况:

在这里插入图片描述
当我们在写代码的时候,就会标红报错。然后我们再运行。

在这里插入图片描述
所以通过上面的饿汉模式实现单例模式是可以成功的,那么我们再来看看懒汉模式如何实现单例模式。

3.2 懒汉模式

前面的为什么要叫做饿汉模式呢?因为饿汉模式定义类的时候,及创建了一个静态的实例,我们都知道静态的成员变量在类加载的时候就会被创建。这样就会导致不管我们用还是没用到这个实例,这个实例都会被创建,会造成内存和时间的浪费。而我们懒汉模式则很好的解决了这个问题,当定义类的时候,我们先不创建这个实例,而是先定义有这个实例,将这个实例赋值为null,当调用 getInstance 方法的时候,判断这个实例是否为 null,如果是 null 则创建实例,为这个实例申请空间和初始化,如果不为空则直接返回。

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

在这里插入图片描述
在这里插入图片描述

但是这样就结束了吗?当然不是,既然是多线程的案例,那么我们肯定要考虑到线程的安全问题,那么接下来我们来看看如何解决单例模式中遇到的线程安全问题。

4. 解决单例模式中遇到的线程安全问题

饿汉模式和懒汉模式是否都会在造成线程不安全问题吗?不是的,因为饿汉模式中只有对变量的判断而没有修改操作,但是懒汉模式中当判断 instance 是否为 null 之后,还会对 instance 做出修改,如果线程中存在判断和修改操作的时候,往往会出现线程不安全问题,所以只有懒汉模式会发生线程不安全的问题。

在这里插入图片描述

4.1 加锁

为了解决在判断和修改的过程中出现线程不安全的问题,需要在这个过程中进行加锁。

class Singleton2 {
    private static Singleton2 instance = null;

    public static Singleton2 getInstance() {
        synchronized (Singleton2.class) {
            if(instance == null) {
                instance = new Singleton2();
            }
        }

        return instance;
    }

    private Singleton2() {}
}

虽然我们在这个过程中进行了加锁,但是这个加锁过程并不是每次调用 getInstance 方法的时候都需要进行加锁,如果加锁频繁的话,那么我们这段代码就与高效率无缘了,只有当第一次调用 getInstance 方法的时候才需要加锁,那么我们又该如何优化这个频繁加锁问题呢?

4.2 加上一个判断解决频繁加锁问题

class Singleton2 {
    private static Singleton2 instance = null;

    public static Singleton2 getInstance() {
        if(instance == null) {
            synchronized (Singleton2.class) {
                if(instance == null) {
                    instance = new Singleton2();
                }
            }
        }

        return instance;
    }

    private Singleton2() {}
}

当再加上一个判断的时候,可能会有人问了,我为了创建一个实例使用了两个相同的判断,那么这个判断不显得多余吗?不多于,这两个判断完全不多余。

  • 第一个判断是判断是否需要加锁,避免频繁加锁
  • 第二个判断是为了判断是否需要创建实例

当实例已经不为 null 的时候,那么因为第一个判断,就不会进行加锁,而是直接返回 instance。

4.2 解决因指令重排序造成的线程不安全问题

只有上面的两个优化是不够的,我们都知道造成线程不安全的问题还有指令重排序的问题。可以将创建实例的过程细分为三个步骤:

  1. 向内存申请空间
  2. 调用构造方法对该内存进行初始化
  3. 将该内存赋值给 instance

如果在创建实例的过程中发生了指令重排序,线程 t1 执行的本应该的顺序为1、2、3,但是却重排序成了1、3、2,那么当线程 t2 和线程 t1 并发执行的时候,就会将没有初始化的引用给返回,从而会出现比较严重的后果。

在这里插入图片描述
所以为了解决指令重排序而发生的线程不安全问题,我们需要使用 volatile 来保证内存的可见性,防止出现指令重排序的发生。

class Singleton2 {
    private volatile static Singleton2 instance = null;

    public static Singleton2 getInstance() {
        if(instance == null) {
            synchronized (Singleton2.class) {
                if(instance == null) {
                    instance = new Singleton2();
                }
            }
        }

        return instance;
    }

    private Singleton2() {}
}

在这里插入图片描述

有了这三个优化,才真正保证了单例模式的安全进行。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【JavaEE】多线程案例-单例模式 的相关文章

随机推荐

  • 【Spring Boot 初识丨三】starter

    上一篇讲了如何构建MAVEN项目 本篇来讲一讲 starter 依赖项 Spring Boot 初识 Spring Boot 初识丨一 入门实战 Spring Boot 初识丨二 maven Spring Boot 初识丨三 starter
  • C——指针与数组名的区别

    昨天晚上做了一套企业面试题 第一题便是 数组名与指针的区别 做了才知道自己知之甚少 学长说像这样的题纸上那点地方是不够用的 而我们能写出来的仅仅是两三行而已 所以特地在网上搜了一下 指针和数组名的共同特点是都是用来指代一个地址的 不同的是
  • 致可爱的仙女程序“媛“们

    谈起程序员 难免大家都会有一些刻板印象 都会觉得在屏幕前猛敲代码的是我们这些五大三粗的大汉 头发那是秃得叫一个地中海 但是我们有的也头发茂密 很帅的好吗 更别说还有很多敲键盘的可是小仙女 说到这里 有些很难让人不生气的是有部分人 居然歧视那
  • HttpSession对象的创建过程

    1 概念 Session代表服务器与浏览器的一次会话过程 这个过程是连续的 也可以时断时续的 在Servlet中 session指的是HttpSession类的对象 这个概念到此结束了 也许会很模糊 但只有看完本文 才能真正有个深刻理解 2
  • idea打war的问题

    大家好 我是雄雄 欢迎关注微信公众号 雄雄的小课堂 前言 今天 记录个到现在为止还没搞清的问题 这个问题浪费了我几个小时的时间 基本上昨天晚上啥也没干 都在弄这个了 主要是还没弄出来 在各个技术群里面也都问了 有的说是项目的jar问题 有的
  • LCD1602液晶显示屏

    介绍 LCD1602液晶显示屏是一种字符型液晶显示模块 可以显示ASCll码的标准字符和其他一些内置的特殊字符 还可以内置8个自定义字符 显示容量 16 2个字符 每个字符为5 7点阵或5 10点阵 一 引脚介绍 VO 对比度调节电压 RS
  • 定位及居中

  • 深度学习入门——神经网络

    神经网络 神经网络是一种受到人脑神经系统启发的机器学习模型 它由一系列相互连接的人工神经元组成 这些神经元以层次结构排列 每个神经元接收来自上一层神经元的输入 并根据权重和激活函数对输入进行加权处理 然后将输出传递给下一层神经元 如下图是一
  • 【随笔】在vue项目使用icon

    Vue引用icon图标 利用i标签 快速添加页面图标 利用i标签 快速添加页面图标 之前写项目遇见图标都是下载成icon然后用img展示 但是图标写多了就会变得特变麻烦 光下载的图标就会占很大空间 所以学着用i写 首先进入项目 在项目下建一
  • python model函数_python--model进阶

    一 QuerySet 1 可切片 使用Python 的切片语法来限制查询集记录的数目 它等同于SQL 的LIMIT 和OFFSET 子句 gt gt gt Entry objects all 5 LIMIT 5 gt gt gt Entry
  • 【动态规划】LCS算法:求两字符串最大公共子序列/删除字符使成为回文串

    问题描述 给定一个字符串s 你可以从中删除一些字符 使得剩下的串是一个回文串 如何删除才能使得回文串最长呢 输出需要删除的字符个数 例如 输入 google 输出 2 思路 回文串通常可以用逆序的方式寻找思路 例如字符串google逆序后e
  • 运维小知识之CDN内容分发网络原理解析

    0x00 前言简述 基础概念 工作原理 组成部分 应用场景 0x01 基础配置 CDN 入门配置 CDN 跨域设置 CDN 响应头参数 扩充 0x02 边缘脚本与程序 EdgeScript 边缘脚本 EdgeRoutine 边缘程序 0x0
  • 【linux】宝塔面板安装命令

    一 查看是否安装 宝塔面板 bt 14 已安装会列出宝塔登录地址 否则 bash bt command not found 下载及安装命令 yum install y wget wget O install sh http download
  • 移动DICT项目是什么?

    DICT项目 我们运营商的伙伴 很多人都知道我们的DICT 但是大家知不知道什么是DICT 你想一想 所谓的DICT 就是指的大数据技术与IT和CT的深度融合 实际上 DICT的可以拆分成三个词 第一个DT 云和大数据的技术 第二个IT 信
  • LeetCode--初级算法--反转链表

    参考 反转链表的方法 反转一个单链表 示例 输入 1 gt 2 gt 3 gt 4 gt 5 gt NULL 输出 5 gt 4 gt 3 gt 2 gt 1 gt NULL 进阶 你可以迭代或递归地反转链表 你能否用两种方法解决这道题 分
  • pandas学习笔记--取表格中特定行或列或特定位置元素

    先生成一个演示dataframe df pd DataFrame np random randn 5 5 columns A B C D E index a b c d e df 取前两行 df 0 2 取后两行 df 2 取倒数第二行 d
  • 什么是DAO,DAO是什么?DAO全面解析

    什么是DAO DAO是什么 DAO是Decentralized Autonomous Organizationd简写 即去中心化自治组织 有时也被称为分布式自治公司 DAC 有共同的目标或是共识 有明确的核心价值观 它的民主化的投票机制 决
  • idea Maven异常:Could not find artifact(本地仓库确实存在)

    首先检查自己的maven环境有无问题 1 首先尝试清楚idea中的缓存如下图所示 当pom没有爆红时 按照上图操作还是无效 可以尝试重新新建一下自己的repository把自己原来的仓库重命名备份下以防万一 我的是这样操作解决了问题 希望能
  • 实战攻防演习之红队

    0x00 什么是红队 红队 一般是指网络实战攻防演习中的攻击一方 红队一般会针对目标系统 人员 软件 硬件和设备同时执行的多角度 混合 对抗性的模拟攻击 通过实现系统提权 控制业务 获取数据等目标 来发现系统 技术 人员和基础架构中存在的网
  • 【JavaEE】多线程案例-单例模式

    文章目录 1 前言 2 什么是单例模式 3 如何实现单例模式 3 1 饿汉模式 3 2 懒汉模式 4 解决单例模式中遇到的线程安全问题 4 1 加锁 4 2 加上一个判断解决频繁加锁问题 4 2 解决因指令重排序造成的线程不安全问题 1 前