iis服务器助手广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >【Java系列】多线程案例学习——单例模式
  • 794
分享到

【Java系列】多线程案例学习——单例模式

java单例模式java-ee 2023-12-23 11:12:35 794人浏览 八月长安
摘要

个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习JavaEE的一点学习心得,欢迎大家

个人主页:兜里有颗棉花糖
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创
收录于专栏【Java系列专栏】【JaveEE学习专栏
本专栏旨在分享学习JavaEE的一点学习心得,欢迎大家在评论区交流讨论💌
在这里插入图片描述

一、设计模式

在讲解单例模式之前,我们先来看一下什么是设计模式。

在实际的软件开发中,我们肯定会碰到很多典型的实际问题来进行解决,而针对这些实际的问题有的人就总结出了特定的一套解决方案来进行问题的解决。

设计模式中就提供给了我们很多典型场景的解决问题的处理方式。

二、单例模式

什么是单例模式:
单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例(比如JDBC中的DataSource实例就只需要一个,即使我们实例出了多个DateSource对象的话我们此时描述的依然是同一个服务器,这是完全没有必要的)。

在单例模式中又分为两种模式,一种是饿汉模式,一种是懒汉模式。

  • 饿汉模式:程序启动,类加载之后就会立即创建出实例。
  • 懒汉模式:在第一次使用实例的时候在创建实例,否则能不创建实例就不创建实例。

饿汉模式

我们来写一段代码进行单例模式(饿汉模式)的举例,请看:

class Singleton {    private static Singleton instance = new Singleton();    public static Singleton getInstance() {        return instance;    }}public class Demo17 {    public static void main(String[] args) {        Singleton s1 = Singleton.getInstance();        Singleton s2 = Singleton.getInstance();        System.out.println(s1 == s2);    }}

代码运行结果如下:
在这里插入图片描述
解释:上述代码使用饿汉式单例模式来实现单例的创建,即在类加载的时候就已经创建好了实例,并且在整个应用程序的生命周期中都只保留了一个实例。
所以最后打印出来的结果是true,因为引用s1和引用s2指向的对象是同一个对象。
再来看下图代码的运行结果:
在这里插入图片描述
引用s1和引用s3指向的对象并不是同一个对象。

现在我们需要对上述代码进行限制,来禁止创建Singleton类的实例(只需要把类Singleton的构造方法的权限设置为private),更改之后的代码如下:
在这里插入图片描述
此时如果我们再向创建Singleton的实例的话就会报错,报错信息如下:
在这里插入图片描述
此时我们能够使用的Singleton实例就有且只有一个了。如果想要获得Singleton的实例的话只能通过get方法来进行获取,并且获取到的对象一定是一个唯一的对象。

懒汉模式

我们依然是写一段代码来进行举例,请看:

class Singletonlazy {    private static Singletonlazy instance = null;    public static Singletonlazy getInstance() {        if(instance == null) {            instance = new Singletonlazy();        }        return instance;    }    private Singletonlazy() {}}public class Demo18 {    public static void main(String[] args) {        Singletonlazy s1 = Singletonlazy.getInstance();        Singletonlazy s2 = Singletonlazy.getInstance();        System.out.println(s1 == s2);    }}

运行结果如下:
在这里插入图片描述
上述代码依旧是无法创建多个实例的,报错信息如下:
在这里插入图片描述
上述写法就是单例模式中的懒汉模式的。

三、线程安全问题

我们现在来看看饿汉模式和懒汉模式中的线程安全问题。

饿汉模式的线程安全问题

在这里插入图片描述

饿汉模式中的线程安全问题解释:当我们多次调用getInstance方法的时候,并不会修改实例instance的内容,同时我们直到多线程读取同一个变量的时候,此时是不会出现线程安全的问题的,因为多线程读取同一个变量的时候是不会对变量进行修改的;因为在这里并不会修改instance实例中的内容。综上,饿汉模式并不会引起线程安全的问题。

懒汉模式的线程安全问题

在这里插入图片描述
线程A获取到,并创建了一个新的Singletonlazy实例并将其赋值给instance。
然后,线程B获取到锁,由于此时instance不为null,线程B也会创建一个新的Singletonlazy实例并将其赋值给instance。这样,就导致了多个实例的创建,违反了单例模式的定义。

在这里插入图片描述
上图就很好的演示了为什么懒汉模式在多线程下会创建出多个实例,即违背了单例模式的初衷。

所以懒汉模式中这里的线程是不安全的。我们可以通过加锁操作来解决上述问题:
请看下图代码,我们能不能通过这种方式来进行加锁呢,请看:
在这里插入图片描述
上述的加锁操作的写法是错误的,并没有解决线程安全问题。
正确的写法是这样的,请看下图:
在这里插入图片描述
在这里插入图片描述
同时我们要知道加锁的基本原则应该是非必要不加锁(加锁本身是一个成本比较高的操作,加锁之后就有可能引起其它线程的等待阻塞)。
在上述正确的加锁之后的代码中每次调用getInstance方法都要进行加锁操作,但是这样的加锁其实是没有必要的(懒汉模式的线程不安全的问题最主要的是出现在实例刚刚创建的时候,一旦实例创建好了之后我们其实就没必要进行加锁操作了,后续再调用getInstance方法的时候也就不存在线程不安全的问题了)。

在这里插入图片描述
请看上图,当实例创建好了之后,getInstance方法中的if判断就永远不会成立,所以上图代码的加锁方式其实是不合适的(因为我们每次调用getInstance方法都需要进行加锁操作),所以我们需要再次对代码进行修改(需要再添加一个if判断)。
修改之后的代码如下图:
在这里插入图片描述
如上图,相同的if判断我们写了两遍,但是这两个if判断之间的执行间隔时间可能是非常长的(加锁操作所引起的阻塞等待时间是不确定的,也有可能时间是非常长的),在这段间隔时间内,其它线程很有可能对instance实例进行修改,所以我们再添加一个if判断是非常有必要的(即一共有两个相同的if判断)。
其实,这两个if判断条件之所以一样也算是一个巧合,第一个if判断是为了判断是否要进行加锁操作,第二个if判断是为了判断是否要创建实例

然而修改后的上述代码依然存在一些其它的问题,比如是否能够保证内存可见性,比如:在一个多线程环境下,线程A和线程B同时调用了getInstance方法,线程A获得了锁,并在检查instance变量为null后,实例化出来一个instance对象,然后将其赋值给instance变量。然而,在这个赋值操作完成之后,由于内存可见性可能无法保证(因为我们不知道编译器是否会对我们的代码进行优化),此时线程B中第二个if判断读取到的instance的值依然是null值,所以第二个线程,即线程B可能也会创建出来一个instance对象。所以此时我们为例能够保证内存可见性,我们此时需要使用volatile对instance变量进行修饰,以保证内存可见性。

另外,上述解释中我们使用了volatile对instance变量进行修饰其实还有另外一个用途,就是防止指令重排序(指令重排序也是线程不安全问题的一个重要原因)。

指令重排序

指令重排序也是编译器对我们代码进行优化的一种手段,即保证代码在原有逻辑不变的情况下,对代码的执行顺序进行一些调整,从而是调整之后的代码的执行效率有所提高。

在这里插入图片描述
如上图,创建出实例对象这个操作站在指令的角度就可以分为三个步骤进行执行:

  • 第一步:给对象创建出内存空间,得到内存地址
  • 第二步:在空间上调用内存方法,对对象进行初始化
  • 第三步:把内存地址赋值给instance引用

上述指令的三个操作由于编译器优化,三个指令的执行顺序就有可能发生改变。
执行完第一步之后对于当单线程而言,先执行第二步还是先执行第三步其实都是可以的。
但是如果是多线程环境下,二、三、步骤的执行顺序进行颠倒的话就有可能出现问题。

举个栗子,假设上述指令是按照132的顺序进行执行的,即还没来得及对对象进行初始化就调度给其它线程了,当第二个线程执行先判定instance不为空然后返回instance的时候,并且可能会使用instance实例对象中的一些属性和方法,但是我们得到的instance对象是一个不完整的对象(即没有进行初始化的对象)。由于这里我们得到的是一个不完整的对象(即该对象并没有被完全的初始化),所以后续我们使用这个对象的时候很大可能就会出现一些问题(因为我们使用的这个对象是一个不完整的对象啊)。
当然,上述出现的问题比较极端,为什么极端我们再来分析一下加深印象:第一点:new对象的指令是按照132的顺序执行的;第二点:在执行3指令之后2指令之前恰好出现了线程调度;第三点:恰好线程调度切换的时候,切换到的那个线程返回了一个未被初始化的一个instance对象。
所以,我们使用volatile对instance变量进行修饰之后就不会出现上述的指令重排序问题,即new对象指令一定会按照123的顺序进行执行。

最终代码版本如下图,请看:
在这里插入图片描述

好了,以上就是关于上述单例模式代码的书写,一共有三个点需要我们注意:加锁操作、双重if的判断、volatile关键字。

本文到这里就结束了,希望友友们可以支持一下一键三连哈。嗯,就到这里吧,再见啦!!!

在这里插入图片描述

来源地址:https://blog.csdn.net/m0_74352571/article/details/135054155

--结束END--

本文标题: 【Java系列】多线程案例学习——单例模式

本文链接: https://www.lsjlt.com/news/551642.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 【Java系列】多线程案例学习——单例模式
    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习JavaEE的一点学习心得,欢迎大家...
    99+
    2023-12-23
    java 单例模式 java-ee
  • Java 多线程系列Ⅳ(单例模式+阻塞式队列+定时器+线程池)
    多线程案例 一、设计模式(单例模式+工厂模式)1、单例模式2、工厂模式 二、阻塞式队列1、生产者消费者模型2、阻塞对列在生产者消费者之间的作用3、用标准库阻塞队列实现生产者消费者模型4、模...
    99+
    2023-09-17
    java 单例模式 阻塞队列 定时器 线程池 并发编程
  • Java多线程常见案例分析线程池与单例模式及阻塞队列
    目录一、单例模式1、饿汉模式2、懒汉模式(单线程)3、懒汉模式(多线程)二、阻塞队列阻塞队列的实现生产者消费者模型三、线程池1、创建线程池的的方法(1)ThreadPoolExecu...
    99+
    2024-04-02
  • Java多线程案例之单例模式懒汉+饿汉+枚举
    目录前言:1.单例模式概述2.单例模式的简单实现2.1饿汉模式2.2懒汉模式2.3枚举实现单例模式前言: 本篇文章将介绍Java多线程中的几个典型案例之单例模式,所谓单例模式,就是一...
    99+
    2024-04-02
  • Java中单例模式与多线程的示例分析
    这篇文章主要介绍了Java中单例模式与多线程的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。单例模式与多线程单例模式就是全局唯一但是所有程序都可以使用的对象写单例模式...
    99+
    2023-06-20
  • Java多线程(单例模式,阻塞队列,定时器,线程池)详解
    目录1. 单例模式(singleton pattern)1.1 懒汉模式1.2 饿汉模式2 阻塞队列(blocking queue)2.1 阻塞队列2.2 生产者消费者模型2.3 标...
    99+
    2024-04-02
  • Java多线程(单例模式,堵塞队列,定时器)详解
    目录一、单例模式饿汉模式懒汉模式懒汉模式 二、堵塞队列实现BlockingQueue三、定时器总结一、单例模式 单例模式是一种设计模式,针对一些特定的场景,研究出对应的解决方案,。...
    99+
    2024-04-02
  • Python学习笔记:单例模式
    单例模式:一个类无论实例化多少次,返回的都是同一个实例,例如:a1=A(), a2=A(), a3=A(),a1、a2和a3其实都是同一个对象,即print(a1 is a2)和print(a2 is a3)都会打印True。 实现方式:...
    99+
    2023-01-30
    学习笔记 模式 Python
  • Java多线程案例之阻塞队列
    文章目录 一. 认识阻塞队列1. 什么是阻塞队列2. 生产者消费者模型3. 标准库中阻塞队列类 二. 基于循环队列实现的简单阻塞队列1. 循环队列的简单实现2. 阻塞队列的简单实现 ...
    99+
    2023-09-01
    java 面试 阻塞队列 生产者消费者模型 多线程
  • Python学习 :反射 & 单例模式
     反射   什么是反射?   - 反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)   面向对象中的反射   - 通过字符串的形式来操作(获取、检查、增加、删除)对象中的成员   - python中的一切事物都是对象...
    99+
    2023-01-31
    反射 模式 Python
  • Java中的多线程如何实现单例模式
    这期内容当中小编将会给大家带来有关Java中的多线程如何实现单例模式,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java多线程中的单例模式一、在多线程环境下创建单例方式一:package com.iet...
    99+
    2023-05-31
    java 线程 单例模式
  • Java零基础学习多线程的示例
    这篇文章给大家分享的是有关Java零基础学习多线程的示例的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。守护线程从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。守护线程是这样的,所有的用户...
    99+
    2023-06-06
  • Java 单例模式线程安全问题
    Java 单例模式线程安全问题SpringIOC容器默认提供bean的访问作用域是单例模式。即在整个application生命周期中,只有一个instance。因此在多线程并发下,会有线程安全风险。我们在MVC框架下的servlet就是线程...
    99+
    2023-05-31
    java 单例模式 线程安全
  • Java多线程案例之线程池
    文章目录 一. 线程池概述1. 什么是线程池2. Java标准库提供的线程池 二. 线程池的简单实现 一. 线程池概述 1. 什么是线程池 线程池和和字符串常量池, 数据库连接池一样,...
    99+
    2023-09-04
    java 线程池 多线程
  • Java设计模式系列之深入浅出单例模式
    目录前言饿汉式懒汉式线程安全问题volatile的作用总结前言 我不知道大家工作或者面试时候遇到过单例模式没,面试的话我记得我当时在17年第一次实习的时候,就遇到了单例模式,面试官是...
    99+
    2024-04-02
  • Java多线程案例之阻塞队列详解
    目录一.阻塞队列介绍1.1阻塞队列特性1.2阻塞队列的优点二.生产者消费者模型2.1阻塞队列对生产者的优化三.标准库中的阻塞队列3.1Java提供阻塞队列实现的标准类3.2Block...
    99+
    2022-11-13
    Java多线程阻塞队列 Java 阻塞队列 Java多线程
  • 【JavaEE】多线程案例-阻塞队列
    1. 前言 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是: 在队列为空时,获取元素的线程会等待队列变为非空当队列满时,存储元素的线程会等待队列可用 阻塞队列...
    99+
    2023-09-16
    java-ee java 开发语言 阻塞队列
  • redis系列:通过队列案例学习list命令
    前言这一篇文章将讲述Redis中的list类型命令,同样也是通过demo来讲述,其他部分这里就不在赘述了。项目Github地址:https://github.com/rainbowda/learnWay/tree/master/learnR...
    99+
    2023-06-04
  • 【Java | 多线程案例】——初识线程池
    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在...
    99+
    2024-01-21
    java 线程池
  • C#单例模式与多线程用法介绍
    一、单例模式 我们先来看看两种创建单例模式的示例代码。 1、饿汉式  饿汉式创建单例模式是在程序里面直接初始化了一个对象实例: class Good { /// &...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作