【JavaEE初阶】第七节.多线程(基础篇)单例模式(案例一)

news2024/11/28 8:28:13

欢迎大家跟我一起来学习有关多线程的有关内容!!!!!!!!!!

文章目录

前言

一、单例模式的概念

二、单例模式的简单实

          2.1 饿汉模式

          2.2 懒汉模式

总结


前言

前面已经向各位铁铁们介绍了 关于多线程的一些基本的知识点,为了可以让大家更好的理解 多线程的一些相关的特性,这篇博客 就会结合着一些具体的代码案例 来向大家介绍;

今天将介绍第一个多线程案例————单例模式;


提示:以下是本篇文章正文内容,下面案例可供参考

 一、单例模式的概念

所谓单例模式,是一种常见的设计模式;

单例模式希望:有些对象,在一个程序中应该只有唯一一个实例,就可以使用单例模式;

换句话说,在单例模式下,对象的实例化被限制了,只能创建一个,多了也创建不了;

如果光是靠人来保证,是不靠谱的,所以就借助语法,强行限制不可以创建多个实例,避免程序员不小心出错;

设计模式:类似于棋谱,是 前辈们已经总结好了的一些固定套路,照着棋谱来下棋,棋就不会下的太差,这就提高了下限(比如说,象棋 ......);

二、单例模式的简单实现

在 Java 里面的单例模式,有很多种实现方式,在这里主要介绍两个大类:饿汉模式、懒汉模式 ;

饿汉模式 和 懒汉模式 两种模式,描述了创建实例的时间;

这两种模式,并没有什么高低贵贱之分,不是 现实生活中的 "贬义词",相反 在计算机领域,"懒"还是一个褒义词,这个字意味着计算机的性能比较高;

在计算机中,这种思想是很常见的;

比如说,想要了解某个资料(大文件 10G,存放在硬盘中),那么 此时使用某个编辑器,打开文件,就会出现两种情况:

[饿汉] 把 10G 都读到内存中,读取完毕之后 再允许用户进行查看和修改
[懒汉] 只读取一点点(当前屏幕能显示出的范围),随着用户翻页,继续再读后续内容

所以说,如果是 饿汉模式,那么显示所需要的时间就会比较多;如果是 懒汉模式,那么显示的时间就会比较低,效率就会比较高(也有可能 用户打开文件以后,只看了两眼就关了,后面的大部分都没有读,那么内存读了那么多也没有意义)

所以,通常我们都认为,懒汉模式 要比 饿汉模式 更高效

当然,还有 刷抖音、看小说、看微信、上网浏览内容 ...... 的时候,都是借鉴了 懒汉模式

2.1 饿汉模式 

饿汉模式 的意思是,程序一旦启动,就会立刻创建实例;

这就好比,一个饿了的人,看到一张饼,就会迫不及待的往嘴里塞,我们把它叫做 "饿汉"

static关键字 的来龙去脉:

static 名字叫做 "静态",但是实际上和字面意思没有任何的关系,这是一个历史遗留的问题

实际表示的含义是 "类属性 / 类方法",同样的,我们把 不是静态的普通的成员叫做 "实例属性 / 实例方法"

Java 里面叫做 "静态" 是因为 C++ 里面表示 "类属性",就是用 static,Java 是从 C++ 那里抄来的;而 C++ 则是因为 引入面向对象之后。需要搞一个方式来定义类属性,就需要引入一个关键字,但是引入新的关键字 成本极高,所以关键字设计者的大佬们 目光就盯住了 旧的关键字

于是,static 就中招了,static 原来表示的是 变量放到静态内存区,但是随着时间的推移,系统的进化,已经没有 "静态内存区" 这个说法了,但是 static关键字 还在,于是 "旧瓶装新酒",就用来表示 "类属性 / 类方法" 了

此时,"类属性 / 类方法" 和 静不静态 字面意思上没有啥关系,只是 随便找一个之前旧的关键字,现在没啥用了,赋予一个新的功能,仅此而已

引入新的关键字成本极高的原因:

写代码的时候,变量名不能和关键字一样,当引入新的关键字的时候,不可以确定 其他人是不是已经引入了 新的关键字 作为变量名(全世界的 C++ 代码那么多 ......)

更大的可能是 新的关键字一引入,就会导致已有的一些代码 编译就会失败

然后这把火就会烧到了 关键字设计者 的身上

而类属性就长在类对象上,类对象在整个程序中只有唯一一个实例(JVM保证的) ,所以说 类的静态成员就只有唯一一个了


饿汉模式代码示例:

package thread;
 
//单例模式,饿汉的方式
class Singleton {
    private static Singleton instance = new Singleton();
 
    //后续如果需要这个实例,就需要统一基于 getInstance 方法来获取 实力独苗,不要去 new 了~
    public static Singleton getInstance() {
        return instance;
    }
 
    //构造方法设为 私有,此时 其他的类想来 new 就不可以了 (通过 编译器的规则来确保只有一个实例对象)~
    private Singleton() { }
}
public class Demo19 {
    public static void main(String[] args) {
        //饿汉模式 的调用
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
    }
}

运行结果:

 如果不小心 想创建另一个实例,那么就会编译报错了:

 前面已经创建过一个实例了:


总结:

使用 静态成员表示实例(唯一性) + 让构造方法设为私有(堵住了 new 创建新实例的口子);

按照上面的代码,当 Singleton类 被加载的时候,就会执行到 实例化操作,此时 实例化的时机非常早(非常迫切的感觉),我们把它称为 饿汉模式;

对于饿汉模式来说,在多线程的情况下,多次调用的是 getInstance() 方法, 而 这个方法只是一个 读操作,对于多线程读操作来说,是线程安全的;

2.2 懒汉模式 

懒汉模式 的意思是,程序启动,先不着急创建实例,等到真正用的时候,再创建实例;

这个也很形象,比较 "懒",不想干活,等到需要的时候再去干活;


代码示例:

//懒汉模式的实现
class SingletLazy {
    //此处没有立即创建实例
    private static SingletLazy instance = null;
 
    //当首次调用 getInstance() 的时候,才会创建实例
    public static SingletLazy getInstance() {
        if (instance == null) {
            instance = new SingletLazy();
        }
        return instance;
    }
 
    //同理,创建构造方法 SingletLazy(),防止该类实例化其他的对象
    private SingletLazy() { }
}

在多线程的情况下,懒汉模式,多次调用 getInstance() 方法,而且涉及到了 两次读操作(读出 instance 是否为空,读出 返回的instance值)和 一次写操作(修改 instance 变量的值),这是线程不安全的;

当然,一旦实例创建好了以后,后续 if 条件语句就进不去了,此时也就是 全是读操作了,也就线程安全了;


既然已经明确了,懒汉模式 是线程不安全的,那么 如何解决懒汉模式线程不安全的问题呢?

办法就是 需要加锁!!!

通过 加锁 来保证 "判断" 和 "修改" 这组操作是原子的;

代码实现:

//懒汉模式的实现
class SingletLazy {
    //此处没有立即创建实例
    private static SingletLazy instance = null;
 
    //当首次调用 getInstance() 的时候,才会创建实例
    public static SingletLazy getInstance() {
        synchronized (SingletLazy.class) {
            if (instance == null) {
                instance = new SingletLazy();
            }
        }
        return instance;
    }
 
    //同理,创建构造方法 SingletLazy(),防止该类实例化其他的对象
    private SingletLazy() { }
}

懒汉模式,只是在初始情况下,才会有线程不安全的问题,一旦实例创建好了以后,此时就安全了;

所以说,在后续调用 getInstance 的时候就不应该尝试加锁了;

如果使用上述的代码,无论 instance 是否为空(是否初始化),都会进行加锁,使得锁竞争加剧,消耗一些没有必要消耗的资源,就会很影响效率了;

在加锁之前,还需要进行判断 instance 是否为空(是否初始化),如果为空才会进行加锁:、

//懒汉模式的实现
class SingletLazy {
    //此处没有立即创建实例
    private static SingletLazy instance = null;
 
    //当首次调用 getInstance() 的时候,才会创建实例
    public static SingletLazy getInstance() {
        if (instance == null) {
            synchronized (SingletLazy.class) {
                if (instance == null) {
                    instance = new SingletLazy();
                }
            }
        }
        return instance;
    }
 
    //同理,创建构造方法 SingletLazy(),防止该类实例化其他的对象
    private SingletLazy() { }
}
 

分析:

外层 if 判定当前是否已经初始化好,如果未初始化好,就尝试加锁;如果已经初始化好,那么就接着往下走; 

里层 if 是在多个线程尝试初始化,产生了锁竞争,这些参与竞争的线程 拿到锁之后,再进一步确认,是否真的要初始化;


当然,上面的代码操作还是有一些问题的 —— 有的线程在读,有的线程在写

这就联想起了 —— 内存可见性问题

其实,这里的情况 和 之前的情况还不一样,每一个线程都有自己的上下文,都有自己的寄存器内容,按理来说 是不应该会出现优化的

但是,实际上也不好说,也并不能保证 编译器优化 是啥样的过程

因此,给 instance 加上 volatile 是更加稳健的做法

如果不加 volatile 不一定会有问题,但是 稳妥起见,还是加上更好

代码实现:

//懒汉模式的实现
class SingletLazy {
    //此处没有立即创建实例
    volatile private static SingletLazy instance = null;
 
    //当首次调用 getInstance() 的时候,才会创建实例
    public static SingletLazy getInstance() {
        if (instance == null) {
            synchronized (SingletLazy.class) {
                if (instance == null) {
                    instance = new SingletLazy();
                }
            }
        }
        return instance;
    }
 
    //同理,创建构造方法 SingletLazy(),防止该类实例化其他的对象
    private SingletLazy() { }
}

总结:

懒汉模式 线程的三个要点:

  1. 加锁
  2. 双重 if
  3. volatile(不加可能是错的,但加了一定是正确的)


总结

今天我们介绍了有关多线程的案例的单例模式的有关内容,下一节我们将继续介绍其他的案例,让我们拭目以待吧!!!!!!!!!

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

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

相关文章

搭建Linux环境

学习Linux之前,我们首先需要在电脑上搭建Linux操作系统环境。 就好比说你买了一台电脑,需要使用Windows10操作系统,那么首先应该安装Windows操作系统(刚买的电脑会引导你一步一步的安装)。 一、Linux环境搭建的三种方式…

数据结构 第三章 栈和队列(队列)

感谢:点击收听 1 基本知识点 1、允许删除的一端称为队头(front) 2、允许插入的一端称为队尾(rear) 3、当队列中没有元素时称为空队列 4、顺序队列: 1 使用顺序表来实现队列 2 两个指针分别指向队列的前端和尾端 **3 如果队列的大小为MaxSize个,那么元…

什么是倒排表(倒排索引)

这种搜索引擎的实现常常用的就是倒排的技术 文档(Document):一般搜索引擎的处理对象是互联网网页,而文档这个概念要更宽泛些,代表以文本形式存在的存储对象,相比网页来说,涵盖更多种形式,比如Word&#xff…

在Mac下如何创建文件

相比于windows中创建Mac是比较复杂的 第一步:打开启动台,依次打开「启动台-其他-自动操作」,可以按住「 Command 空格」直接搜索「自动操作」程序。 第二步:打开之后在「选取文稿类型」选项时,选择「快速操作」&#…

工地车辆未冲洗识别抓拍系统 yolov5网络

工地车辆未冲洗识别抓拍系统通过yolov5网络深度算法学习模型,自动对画面中每辆进出车辆的清洗实现自动识别判定。如果识别到车辆冲洗不合格,就会自动进行抓拍并将违规车辆信息回传。目标检测架构分为两种,一种是two-stage,一种是o…

「兔了个兔」看我如何抓取兔兔图片到本地(附源码)

💂作者简介: THUNDER王,一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读,同时任汉硕云(广东)科技有限公司ABAP开发顾问。在学习工作中,我通常使用偏后…

如何在IDEA中使用Maven构建Java项目?Maven的使用详细解读

文章目录1. 前言2. IDEA 中配置 Maven 环境3. Maven 的坐标问题4. IDEA 中创建 Maven 项目5. IDEA 中导入 Maven 项目6. 安装插件7. 依赖管理8. 依赖范围6. 总结Java编程基础教程系列1. 前言 前面在如何使用 Maven 构建 Java 项目一文中,我们一直在命令行中执行构建…

LabVIEW什么是实时操作系统(RTOS)

LabVIEW什么是实时操作系统(RTOS)一般而言,操作系统的任务是管理计算机的硬件资源和应用程序。实时操作系统会执行这些任务,但是运行时间精度和可靠度都极高。在实际应用中,有的系统失常代价高昂,甚至会引起安全事故。这时&#x…

成为提示专家,AI艺术杂志:AI Unleashed 第一期

shadow最近发现了一期AI艺术的杂志。名称叫 AI Unleashed,是一本深入探索 AI 和想象力的杂志。每期杂志都将填满精彩的 AI 艺术,激发你的好奇心, 让你更加了解最新的 AI 技术,以及它如何改变现有工作流和我们对艺术和技术的看法。…

LVS+keepalived(双主)+Nginx实现高可用负载均衡

#为什么采用双主架构: 单主架构只有一个keepalived对外提供服务,该主机长期处于繁忙状态,而另一台主机却很空闲,利用率低下 #双主架构的优点: 即将两个或以上VIP分别运行在不同的keepalived服务器,以实现…

C++11使用线程类thread的方法

C11 之前,C 语言没有对并发编程提供语言级别的支持。如果需要使用线程,windows系统需要使用CreateThread函数创建线程,而linux需要使用pthread库使用线程。C11 中增加了线程以及线程相关的类,很方便地支持了并发编程。由于可以跨平…

活动星投票十大商业品牌网络评选微信的投票方式线上免费投票

“十大商业品牌”网络评选投票_线上系统免费投票_功能齐全的视频投票_在线投票免费小程序用户在使用微信投票的时候,需要功能齐全,又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便,用户可以随时使用手机微信小程序获得线上投…

CV学习笔记-VGG

VGG 1. 常见的卷积神经网络 VGG属于一种经典的卷积神经网络结构,其出现在AlexNet之后,由于AlexNet的突破证实了卷积神经网络的可行性,VGG的思路主要是将网络层数加深,从某种意义上说,网络层数的加深可以粗略地认为考虑…

编译原理学习笔记14——属性文法与语法制导翻译1

编译原理学习笔记14——属性文法与语法制导翻译114.1 属性文法14.2 属性计算14.1 属性文法 属性文法 综合属性 自下而上传递信息语法规则:根据右 部候选式中的符号 的属性计算左部被 定义符号的综合属性语法树:根据子结 点的属性和父结点 自身的属性…

【日常系列】LeetCode《30·动态规划总结》

动态规划总结 线性动态规划问题总结 打家劫舍总结 最大子数组和总结 dp[i] 依赖于前面一个或者两个状态 dp[i] 依赖于前面多个状态 注意:子序列可以不连续 dp[i] 带有一个或者多个维度 输入为两个数组或者两个字符串 lc 10【剑指 19】【top100】:正…

【LeetCode每日一题:2309. 兼具大小写的最好英文字母~~~模拟+Hash表+贪心】

题目描述 给你一个由英文字母组成的字符串 s ,请你找出并返回 s 中的 最好 英文字母。返回的字母必须为大写形式。如果不存在满足条件的字母,则返回一个空字符串。 最好 英文字母的大写和小写形式必须 都 在 s 中出现。 英文字母 b 比另一个英文字母 …

Java生成微信小程序二维码,5种实现方式,一个比一个简单

文章目录前言先看官网一、JDK自带的URLConnection方式二、Apache的HttpClient方式三、okhttp3方式四、Unirest方式五、RestTemplate方式其它细节getAccessToken构建参数mapbyte[]数组源码下载前言 先介绍一下项目场景,主要是通过微信小程序二维码裂变分享&#xff…

一时重构一时爽,一直重构一直爽

笔者(后台技术汇)恭祝各位大佬:2023年春节快乐,兔年祥瑞。距离上次更新,已经过去5个月有余了,有小伙伴疑惑笔者是不是删库跑路了..其实不是,这段时间是在参加一次比较大的项目重构(目…

学习笔记 —— python代码耗时及内存占用测试方法

1、手写耗时测试 先看结果; 主要有三种方法,各自的时钟间隔如下: time.time() timeit time.time_ns() ( time is outputted in ns!). 可见方法2,即timeit 的时钟间隔最短。 注:最后一个是以ns为单位的,前两个是…

二叉树的概念与结构

文章目录前言一、树的概念及结构1.树的概念2.树的相关概念3.树的表示4. 树在实际中的运用二、二叉树概念及结构1.概念2.特殊的二叉树5.二叉树的性质6.二叉树的存储结构(1).顺序存储(2).链式存储结语前言 因为二叉树的知识点太多,一篇文章讲不…