【Java EE初阶六】多线程案例(单例模式)

1. 单例模式

        单例模式是一种设计模式,设计模式是我们必须要掌握的一个技能;

1.1 关于框架和设计模式

        设计模式是软性的规定,且框架是硬性的规定,这些都是技术大佬已经设计好的;

        一般来说设计模式有很多种,且不同的语言会有不同的设计模式,(同时设计模式也可以理解为对编程语言的一种补充

1.2 细说单例模式

        单例 = 单个实例(对象);

        某个类,在一个线程中,只应该创建一个实例化对象(原则上不应该有多个),这时就使用单例模式,如此可以对我们的代码进行一个更严格的校验和检查。

        保证对象唯一性的方法:

        方法一,可以通过“协议约束”,写一个文档,规定这个类只能有唯一的实例,程序员在接手这个代码时,就会发现这个文档已经进行约定,其中的规定约束着程序员在创建对象时,时刻注意只能创建一个对象。

        方法二:从机器入手;让机器帮我们检查,我们期望让机器帮我们对代码中指定的类,创建类的实例个数进行检查、校验,当创建的实例个数超过我们期望个数,就编译报错。其中单例模式就是已经设计好的套路,可以实现这种预期效果。

        关于单例模式代码实现的基本方式有两种:饿汉模式和懒汉模式;

2. 饿汉模式

        饿汉模式是指创建实例的时期非常早;在类加载的时候,程序一启动,就已经创建好实例了,使用 “饿汉”这个词,就是形容创建实例非常迫切,非常早。单例模式代码如下:

class Singleton {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton(){ }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Singleton singleton = new Singleton();
    }
}

        当我们运行该代码时,系统就会报错,接下来我们详细的分析一下此处的代码; 

        这样,如果我们想new一个Singleton对象,也new不了,同时不管我们用getInstance获取多少次实例,获取的对象都是同一个对象,代码如下:

package thread;

// 就期望这个类只能有唯一的实例 (一个进程中)
class Singleton {
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() {}
}

public class ThreadDemo26 {
    public static void main(String[] args) {
        // Singleton s = new Singleton();
        Singleton s = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s == s2);
    }
}

        结果如下:

3. 懒汉模式

        和饿汉模式不一样的是,懒汉模式创建实例的时机比较晚,没饿汉创建实例那么迫切,只有第一次使用这个类时,才会创建实例,代码如下:

class SingletonLazy {
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if(instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy() { }
}
public class TestDemo5 {
    public static void main(String[] args) {
 
    }
}

        下面为代码图解分析:

        和饿汉模式的区别就是没那么迫切创建实例,等需要调用这个类的时候才创建一个实例,而饿汉模式是有了这个类就创建出实例。

        懒汉模式的优点:有的程序,要在一定条件下,才需要进行相关的操作,有时候不满足这个条件,也就不需要完成这个操作了,如此哦·就把这个操作省下来了。

4. 两种模式关于线程安全

4.1 饿汉模式

        线程安全;

        对于饿汉模式来说,上图所示通过调用getinstance方法来返回instance对象,本质上来说是读操作;

        当有多个线程,同时并发执行,调用getInstance方法,取instance,这时线程是安全的,因为只涉及到读,多线程读取同一个变量,是线程安全的。而instance很早之前就已经创建好了,不会修改它,一直也只有这一个实例,也不涉及写的操作。

4.2 懒汉模式

        线程不安全;

        在懒汉模式中,条件判定和返回时是读操作,new一个对象是写操作;

       我们只有调用getInstance方法后,就会创建出实例来,如果多个线程同时调用这个方法,此时SingletonLazy类里面的instance都为null,那么这些线程都会new对象,就会创建多个实例。这时,就不符合我们单例模式的预期了,所以,这个代码是线程不安全的。

        线程不安全的直接原因,就是 “写” 操作不是原子的。

4.3 解决懒汉模式的线程安全问题

4.3.1 把写操作打包成原子

        因为多线程并发执行的时候,可能读到的都是instance == null,所以会创建多个实例,那我们就给它加锁,让它在创建实例的时候,只能创建一个,加锁代码如下:

class SingletonLazy {
    private static Object locker = new Object();
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        synchronized (locker) {
            if(instance == null) {
                instance = new SingletonLazy();
            }
        }
        return instance;
    }
    private SingletonLazy() { }
}

        以上操作虽然将写操作打包成了一个原子,但是新的问题也出现了;

4.3.2 去除冗余操作

         上述操作加上了还是有问题:如果已经创建出实例了,我们还有加锁来判断它是不是null吗,加锁这些操作也是要消耗硬件资源的,没有必要为此浪费资源空间,如果已经不是null了,我们就想让它直接返回,不再进行加锁操作,代码修改如下:

class SingletonLazy {
    private static Object locker = new Object();
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() { }
}

        代码图解分析两个判断语句的是目的意义:

4.3.3 指令重排序的问题

        指令重排序:指令重排序也是编译器的一种优化,在保证原代码的逻辑不变,调整原代码的指令执行顺序,从而让程序的执行效率提高。

        保证原代码的逻辑不变,改变原有指令的顺序,从而提高代码的执行效率,其中这个代码,就存在着指令重排序的优化,如下图代码:

该语句原本指令执行顺序:

        1、去内存申请一段空间

        2、在这个内存中调用构造方法,创建实例

        3、从内存中取出地址,赋值给这个实例instance。

指令重排序后的顺序:1, 3 , 2;按照指令重排序后的代码执行逻辑就变成了下面所示:

        假设有两个线程,现在执行顺序如下图所示:

        因为指令重排序后,先去内存申请一段空间,然后是赋值给instance,那这时,instance就不是null了,第二个线程不会进入到if语句了,直接返回instance,可是instance还没有创建出实例,这样返回肯定是有问题的,如此也就线程不安全了。

        解决方案:

        给instance这个变量,加volatile修饰,强制取消编译器的优化,不能指令重排序,同时也排除了内存可见性的问题。

        加volatile后的代码如下:


 

class SingletonLazy {
    private static Object locker = new Object();
    private static volatile SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() { }
}

        至此,我们才算解决掉懒汉模式关于线程安全的所有问题;

4.4 懒汉模式线程安全的代码

package thread;

// 懒汉的方式实现单例模式.
class SingletonLazy {
    // 这个引用指向唯一实例. 这个引用先初始化为 null, 而不是立即创建实例
    private volatile static SingletonLazy instance = null;
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        // 如果 Instance 为 null, 就说明是首次调用, 首次调用就需要考虑线程安全问题, 就要加锁.
        // 如果非 null, 就说明是后续的调用, 就不必加锁了.
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() { }
}

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

        结果如下:

ps:本次的内容就到这里了,如果感兴趣的话,就请一键三连哦!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

抖音字幕视频怎么做能滚动 抖音个性字幕怎么做 抖音短视频用什么软件剪辑

不管是抖音短视频,还是其他影视网站的影视剧,字幕基本都是必不可少的,字幕本身就能加强观众对视频的理解,而且像一些滚动字幕,会更加吸引观众的注意力,那抖音字幕视频怎么做能滚动?抖音个性字幕…

Hierarchical Clusting模型

介绍: Hierarchical Clustering 是一种常用的聚类方法,它通过构建一个层次化的聚类树(或者称为聚类图),将数据点逐步合并组成不同的聚类簇。 Hierarchical Clustering 的主要思想是将相似的数据点归为一类&#xff0c…

动态规划(不同路径1,不同路径2,整数拆分)

62.不同路径 力扣题目链接(opens new window) 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。…

有什么安全处理方案可以有效防护恶意爬虫

常见的爬虫 有百度爬虫、谷歌爬虫、必应爬虫等搜索引擎类爬虫,此类爬虫经常被企业用于提高站点在搜索引擎内的自然排名,使得站点在各大搜索引擎中的排名能够提高,进一步通过搜索引擎来进行引流为企业增加业务流量。 恶意爬虫与合法、合规的搜…

资源类的使用(MFC)

文章目录 1.预备知识1.菜单1.创建菜单在系统自动生成的菜单资源中添加一个主菜单命令菜单属性 2.编辑菜单过程中所涉及的操作3.菜单设计步骤4.菜单的响应和消息路由5.CMenu类获取菜单指针添加菜单项删除菜单项获取菜单项数目获取菜单ID号对菜单项属性的修改显示快捷菜单 2.工具…

Reids原理及简单命令

目录 1.关系数据库与非关系型数据库 关系型数据库 非关系型数据库 关系型数据库和非关系型数据库区别 数据存储方式不同 扩展方式不同 对事务性的支持不同 总结: 2. Redis简介 什么是redis reids优点 reids使用场景: reids快的原因 Redis数…

Ubuntu 虚拟机挂接 Windows 目录

Windows 共享目录 首先 Windows 下共享目录 我这里偷懒直接直接 Everyone ,也可以指定用户啥的 Ubuntu 挂接 挂接命令,类似如下: sudo mount -o usernamefananchong,passwordxxxx,uid1000,gid1000,file_mode0644,dir_mode0755,dynperm //…

不要告诉别人的passwd

文章目录 不要告诉别人的passwd修改或更新密码删除用户密码查看密码的状态更多信息 不要告诉别人的passwd passwd用于创建或者更新用户密码,是管理员必备的命令之一。 这个命令最终的实现是通过调用Linux-PAM 和Libuser API来实现的。 官方的定义为: …

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -小程序首页实现

锋哥原创的uniapp微信小程序投票系统实战: uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

【2023 CCF 大数据与计算智能大赛】基于TPU平台实现超分辨率重建模型部署 基于QuickRNet的TPU超分模型部署

2023 CCF 大数据与计算智能大赛 《赛题名称》 基于QuickRNet的TPU超分模型部署 巴黎欧莱雅 林松 智能应用业务部算法工程师 中信科移动 中国-北京 gpu163.com 团队简介 巴黎欧莱雅团队包含一个队长和零个队员。 队长林松,研究生学历,2019-202…

【C++】内存对齐

本篇文章介绍C中的内存对齐,后续介绍C的union和C的variant的时候,需要用到这部分的知识。 占用内存 先回忆下C各个数据类型占用的内存大小: int:所占内存大小:4byte 32bit;char:所占内存大小…

视频智能分析支持摄像头异常位移检测,监测摄像机异常位移变化,保障监控状态

我们经常在生产场景中会遇到摄像头经过风吹日晒,或者异常的触碰,导致了角度或者位置的变化,这种情况下,如果不及时做出调整,会导致原本的监控条件被破坏,发生事件需要追溯的时候,查不到对应位置…

Kubernetes复习总结(一):Kubernetes内置资源、Device Plugin机制

1、Kubernetes内置资源 1)、Pod Pod是Kubernetes进行管理的最小单元,程序要运行必须部署在容器中,而容器必须存在于Pod中 Pod可以认为是容器的封装,一个Pod中可以存在一个或者多个容器 1)Pod进程组 在Kubernetes里面…

cookie和session、请求转发和重定向

会话 分为有状态会话和无状态会话 在HTML中,"会话"一般指的是Web服务器与客户端(通常是浏览器)之间进行的一系列请求和响应。它是一种在网络上模拟人与人之间通信的方式,常见于Web应用程序中。 会话、Cookie和Sessio…

Vue电商后端管理API接口测试

引言 最近有人在学习接口自动化测试时没有接口练手,其实接口的话,要么找第三方提供的,要么自己开发。第三方在线API需要认证,并且普通的话每天调用次数有一定的限制。自己开发的话,只要不停电,想怎么用就怎…

Jmeter接口自动化测试 :Jmeter变量的使用

在使用jmeter进行接口测试时,我们难免会遇到需要从上下文中获取测试数据的情况,这个时候就需要引入变量了。 定义变量 添加->配置元件->用户自定义的变量 添加->配置元件->CSV 数据文件设置 变量的调用方式:${变量名} 变量的作…

低代码平台的崛起:探索火爆背后的因素

文章目录 前言低代码开发平台优缺点有哪些?速度稳定性赋能一致性安全简单低代码为什么能火?由哪些因素导致? 低代码的优势后记 前言 在当前科技发展快速的时代,低代码开发平台越来越受到关注和推崇。与传统的软件开发方式相比&am…

C++学习笔记——类作用域和抽象数据类型

目录 一、C类作用域 类内作用域 类外作用域 二、类作用域案列详细的解释说明 三、抽象数据类型 四、总结 类作用域 抽象数据类型(ADT) 五、图书馆管理系统 一、C类作用域 在C中,类作用域是指类定义中声明的标识符(成员变…

我建立了一个资源分享群

我建立了一个资源分享群 在为寻找资源犯愁? 在为分享资源犯愁? 一起加入分享资源群(是wx群哦)吧!你可以分享自己的资源帮助他人。你可以在群组里需求资源获取别人的帮助。发广告请绕行,会被拉黑哦 微信…

基于SpringBoot+Vue人力资源管理系统(前后端分离)

该项目完全免费 系统介绍 基于 SpringBootVue 实现的人力资源管理系统是为了提高企业人力资源管理水平而开发的。主要目标是通过对员工 及人力资源活动信息(考勤、工资 ) 等的编制来提高企业效率。 系统一共分为五大菜单项,分别是首页、薪资管理、权…