javaEE初阶——多线程(四)

news2024/11/24 14:47:20

在这里插入图片描述

T04BF

👋专栏: 算法|JAVA|MySQL|C语言

🫵 小比特 大梦想

此篇文章与大家分享多线程专题的第四篇(关于多线程代码案例中的单例模式)
如果有不足的或者错误的请您指出!

目录

  • 九、多线程代码案例(单例模式)
    • 1.单例模式
      • 1.1饿汉模式
      • 1.2懒汉模式
      • 1.3使用场景
      • 1.4上述单例模式的线程安全问题
      • 1.5指令重排序问题

九、多线程代码案例(单例模式)

1.单例模式

单例模式是设计模式里面比较简单的一种,顾名思义,单例就是只有一个实例,也就是对于进程中的某个类,它只能实例化一个对象,不会new出来多的对象
那我们如何保证一个类只能有一个对象呢???单凭我们口头保证肯定是不行的,我们就需要通过特定代码的手段来实现这个功能
单例模式我们主要认识两种写法:饿汉模式、懒汉模式

1.1饿汉模式

表示这个类的唯一实例创建得比较早,类似于饿了很久的人,一看到吃的就迫不及待想去吃
用代码体现就是:

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

static修饰的其实是类属性,就是在"类对象"上的,每个类的类对象在jvm中只有一个,那么里面的静态成员就只有一个了
此处后续需要使用这个类的实例,就可以直接通过getInstance来获取已经new好的这个,而不是重新new
但是我们怎么保证,这个类就只能实例化一次呢??我们直接将构造方法私有化即可

public class Singleton {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
    private Singleton(){}//代码里面不需要有任何逻辑
}

将构造方法私有化是单例模式里面最核心的一步,类之外的代码尝试实例化对象的时候,就必须要通过构造方法,但是由于构造方法是私有的,无法调用,尝试实例化的时候就会编译出错
我们验证一下:
在这里插入图片描述
当我们尝试实例化对象:
在这里插入图片描述
就会编译出错

可能有人会问两种极端情况
(1)如果我在一个Singleton类里面实例化多个对象,那不就打破了单例模式了嘛??
实际上,如果你是这个类的作者,你一定不会去这么做;如果你是别人,使用这个类的时候,如果要进行修改,一般也要经过你的审核
(2)那么我们可以通过反射机制拿到私有的构造方法嘛??
原则上是可以的,但是在实际开发中,反射机制并不常见,甚至不能乱用,特殊场景下回需要用到反射,但是使用反射要付出很大的代价(会严重影响代码的可读性和封装性)

1.2懒汉模式

在计算机领域,"懒"往往是提高效率的表现
在懒汉模式中,不是在程序启动的时候就实例化好唯一对象了,而是在后续使用这个类的时候才去创建实例,此时如果不去使用这个类,那么创建实例的代价就很好地省下了

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

    private SingletonLazy(){}
    
}

什么时候使用这个类就什么时候创建这个实例,本质上就是能偷懒的,能少做的就少做

1.3使用场景

代码中的有些对象,本身就不适合是有多个实例的,从业务角度就应该是单个实例

比如,你写的服务器,要从硬盘上加载100G的数据到内存里面,肯定要写一个类,封装上述加载操作,并且写一些获取/处理业务数据的业务逻辑

这样的类就应该是单例的,一个实例就管理100G的数据,搞N个实例就是N*100G的内存数据,机器肯定吃不消,也没必要,因为都是重复的数据

例如在java的JDBC中,DataSourse就应该是单例的,这种对象就类似于配置管理的对象(存储服务器的地址,端口号,用户名,密码,选项参数…)

1.4上述单例模式的线程安全问题

我们需要考虑,如果有多个线程同时调用getInstance(),线程是否安全??
如果是在饿汉模式下,实例创建的时间是程序启动的时候,比main线程调用还早,因而后续使用这个类的时候,调用getInstance()一定比创建实例要晚,此时就只是读取上述变量的值了,而在多个线程里同时读取同一个变量,是不会有线程安全问题的
我们主要是来看懒汉模式下的线程安全问题
在这里插入图片描述
此时一旦出现这种执行顺序,那么就会创建多一个实例
因此我们需要做的就是将if操作和new操作打包成一个原子操作
那么就要进行加锁操作

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

    private SingletonLazy(){}

}

但是还有一个细节问题,我们知道在懒汉模式下,只有在第一次调用getInstance()才是创建实例的.而一旦创建好了,后面的就只是读操作了
但是如果我们单纯这样加,那么后面尽管是读,也是需要加锁的,而加锁的开销是很大的,也有可能导致线程堵塞
因此我们可以在外层在加上if条件判断,如果需要创建实例,才加锁

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

    private SingletonLazy(){}

}

此时两个if的作用是不一样的,外层的if是为了防止没必要的加锁操作,里层的if是判断要不要加锁(如果两个线程都进去了第一层if,那么第二层if就是防止多创建对象)

同时,为了防止编译器进行优化操作,我们还需要对instance加上volatile关键字

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

    private SingletonLazy(){}

}

1.5指令重排序问题

指令重排序实际上也是编译器优化的一种策略
我们写的代码在被编译成一条一条的二进制指令后,正常来说CPU都是按照顺序一条一条执行,但是编译器比较智能,会根据实际情况,调整指令执行的顺序,调整的目的就是为了提高效率
在单线程下,编译器的优化策略是比较安全的,能够保证优化前后代码的逻辑不变,但是在多线程环境下,就可能出现问题
就拿我们上面的单例模式 - 懒汉模式来说

instance = new SingletonLazy();

这一句代码,我们简单来看就可以分成3个步骤
(1)申请内存空间
(2)调用构造方法
(3)把此时内存的地址赋值到instance
在指令重排序下,可能会出现1,2,3或者1,3,2两种执行顺序(但是1一定是再前面的),在单例模式下这两种都是OK的
但是在多线程就可能会出现下面的情况:
在这里插入图片描述
在t1线程进行完地址赋值操作后,意味着instance就是非null,只是此时执行的对象是一个未初始化的对象
此时t2线程恰好进来,由于instance已经非空,第一个if的条件无法满足,就直接return了,如果在这个时候,进行Singleton s = …,s.func(),这里的后续操作都是针对未初始化的对象进行操作的,会出现严重的问题

解决上述问题的方法还是使用volatile
即volatile不仅能够解决内存可见性的线程安全问题,还能解决指令重排序的问题

感谢您的访问!!期待您的关注!!!

在这里插入图片描述

T04BF

🫵 小比特 大梦想

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

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

相关文章

Elasticsearch安装、启动异常问题总结

安装es、kibana、ik分词器可参考:http://t.csdnimg.cn/59mEG 1. 内核过低 我们使⽤的是 centos6 ,其 linux 内核版本为 2.6 。⽽ Elasticsearch 的插件要求⾄少 3.5 以上版 本。不过没关系,我们禁 ⽤这个插件即可。 修改 elasticsearch.ym…

车内AR互动娱乐解决方案,打造沉浸式智能座舱体验

美摄科技凭借其卓越的创新能力,为企业带来了革命性的车内AR互动娱乐解决方案。该方案凭借自研的AI检测和渲染引擎,打造出逼真的数字形象,不仅丰富了车机娱乐内容,更提升了乘客与车辆的互动体验,让每一次出行都成为一场…

.NET JWT入坑

前言 JWT (JSON Web Token) 是一种安全传输信息的开放标准,由Header、Payload和Signature三部分组成。它主要用于身份验证、信息交换和授权。JWT可验证用户身份,确保访问权限,实现单点登录,并在客户端和服务器之间安全地交换信息…

HarmonyOS实战开发-音视频录制、如何实现音频录制和视频录制功能的应用

介绍 音视频录制应用是基于AVRecorder接口开发的实现音频录制和视频录制功能的应用,音视频录制的主要工作是捕获音频信号,接收视频信号,完成音视频编码并保存到文件中,帮助开发者轻松实现音视频录制功能,包括开始录制…

【JavaWeb】异步请求——AJAX

目录 Ajax(Asynchronous JavaScript and XML)优点传统Web与Ajax的差异Ajax工作流程Ajax 经典应用场景XMLHttpRequest常用方法事件常用属性 ajax: GET请求和POST请求的区别 传统Ajax实现传统方式实现Ajax的不足 $.ajax()语法常用属性参数常用函数参数 Aja…

Docker 镜像仓库常见命令

Docker Registry (镜像仓库) 常用命令 docker login 功能:登录到一个 Docker 镜像仓库,如果没有指定镜像仓库的地址,默认就是官方的 Docker Hub 仓库。 语法: docker login [options] [server]选项: -u:登…

Missing artifact org.opencv:opencv:jar:4.10.0 [opencv-4.10.0.jar]

Missing artifact org.opencv:opencv:jar:4.10.0 [opencv-4.10.0.jar] https://mvnrepository.com/artifact/org.opencv/opencv 根本就没有 找了个旧项目的opencv-410.jar修改下opencv-4.10.0.jar放到目录下面就好了 D:\localRepository\org\opencv\opencv\4.10.0 OpenCV-C…

Failed to delete XXXX.jar

Failed to delete XXXX.jar 问题:idea控制台报Failed to clean project:Failed to delete idea中点击maven->对应pom->lifecycle->clean时,报错 原因:target文件可能时编译的文件被其他程序占用,导致资源无法回收 解…

u盘为什么一插上电脑就蓝屏,u盘一插电脑就蓝屏

u盘之前还好好的,可以传输文件,使用正常,但是最近使用时却出现问题了。只要将u盘一插入电脑,电脑就显示蓝屏。u盘为什么一插上电脑就蓝屏呢?一般,导致的原因有以下几种。一,主板的SATA或IDE控制器驱动损坏…

农业现代化:UWB模块为农业领域带来的效益和便利

随着科技的进步和农业现代化的推进,超宽带(UWB)技术正逐渐在农业领域发挥重要作用。UWB模块作为UWB技术的核心组成部分,具有高精度、实时性强的特点,为农业生产提供了新的技术手段和解决方案。本文将探讨UWB模块在农业…

WSL访问adb usb device

1.Windows上用PowerShell运行: winget install --interactive --exact dorssel.usbipd-win 2.在WSLUbuntu上终端运行: sudo apt install linux-tools-generic hwdata sudo update-alternatives --install /usr/local/bin/usbip usbip /usr/lib/linux-too…

最优算法100例之44-不用加减乘除做加法

专栏主页:计算机专业基础知识总结(适用于期末复习考研刷题求职面试)系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 不用加减乘除做加法 题解报告 最优解法:使用异或 1)异或是查看两个数哪些二进制位只有一个为1,这些是非进位位,可以直接…

界面控件DevExpress WinForms/WPF v23.2 - 富文本编辑器支持内容控件

众所周知内容控件是交互式UI元素(文本字段、下拉列表、日期选择器),用于在屏幕上输入和管理信息。内容控件通常在模板/表单中使用,以标准化文档格式和简化数据输入。DevExpress文字处理产品库(Word Processing Document API、WinForm和WPF富文…

(文章复现)考虑网络动态重构的分布式电源选址定容优化方法

参考文献: [1]朱俊澎,顾伟,张韩旦,等.考虑网络动态重构的分布式电源选址定容优化方法[J].电力系统自动化,2018,42(05):111-119. 1.摘要 以投资周期经济收益最高为目标,基于二阶锥规划提出了一种考虑网络动态重构的分布式电源选址定容优化方法。首先&am…

【Proteus仿真】按键控制LED流水灯定时器时钟

0~65535 每隔1us计数加1 总共定时时间65535us 64535离计数器溢出差值1000&#xff0c;所以计时时间为1ms #include <REGX51.H> void inittimer0() {TMOD0x01;//0000 0001TF00;//SCON可位寻址&#xff0c;TF1产生中断TR01;//定时器启动TL064535%256;//定时1msTH064536/256…

OSPF的P2P和Broadcast

OSPF为什么会有P2P和BROADCAST两种类型 OSPF&#xff08;开放最短路径优先&#xff09;协议中存在P2P&#xff08;点对点&#xff09;和BROADCAST&#xff08;广播多路访问&#xff09;两种网络类型&#xff0c;主要是为了适应不同类型的网络环境和需求。具体分析如下&#xf…

云原生(八)、Kubernetes基础(一)

K8S 基础 # 获取登录令牌 kubectl create token admin --namespace kubernetes-dashboard1、 NameSpace Kubernetes 启动时会创建四个初始名字空间 default:Kubernetes 包含这个名字空间&#xff0c;以便于你无需创建新的名字空间即可开始使用新集群。 kube-node-lease: 该…

飞书API(3):Python 自动读取多维表所有分页数据的三种方法

上一小节介绍了怎么使用 Python 读取多维表的数据&#xff0c;看似可以成功获取到了所有的数据&#xff0c;但是在实际生产使用过程中&#xff0c;我们会发现&#xff0c;上一小节的代码并不能获取到所有的多维表数据&#xff0c;它只能获取一页&#xff0c;默认是第一页。因为…

算法打卡day33

今日任务&#xff1a; 1&#xff09;509. 斐波那契数 2&#xff09;70. 爬楼梯 3&#xff09;746.使用最小花费爬楼梯 509. 斐波那契数 题目链接&#xff1a;509. 斐波那契数 - 力扣&#xff08;LeetCode&#xff09; 斐波那契数&#xff0c;通常用 F(n) 表示&#xff0c;形成…

分布式技术---------------消息队列中间件之 Kafka

目录 一、Kafka 概述 1.1为什么需要消息队列&#xff08;MQ&#xff09; 1.2使用消息队列的好处 1.2.1解耦 1.2.2可恢复性 1.2.3缓冲 1.2.4灵活性 & 峰值处理能力 1.2.5异步通信 1.3消息队列的两种模式 1.3.1点对点模式&#xff08;一对一&#xff0c;消费者主动…