【JavaEE初阶】 线程安全

文章目录

  • 🌴线程安全的概念
  • 🌳观察线程不安全
  • 🎄线程不安全的原因
    • 🚩修改共享数据
    • 📌原子性
    • 📌 可见性
    • 📌代码顺序性
  • 🌲解决之前的线程不安全问题
  • ⭕总结

🌴线程安全的概念

线程安全是多线程编程是的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且准确的执行,不会出现数据污染等意外情况。上述是百度百科给出的一个概念解释。换言之,线程安全就是某个函数在并发环境中调用时,能够处理好多个线程之间的共享变量,是程序能够正确执行完毕。也就是说我们想要确保在多线程访问的时候,我们的程序还能够按照我们的预期的行为去执行,那么就是线程安全了。

我们可以这样认为:

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该得到的结果,则说这个程序是线程安全的。

🌳观察线程不安全

现在有以下代码,调用两个线程,两个线程对同一个对象的同一元素进行加加操作,该元素初始值为0,每个线程加50000次,我们观察最终结果

代码如下:

 class Count {public int count = 0;void increase() {count++;}
}
public class Counter {public static void main(String[] args) throws InterruptedException {final Count count = new Count();//搞两个线程,分别对count进行++操作,每一个线程加50000次Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count.count);}
}

我们预期的结果是count = 100000,但是我们运行后发现,结果无法预料

下面是博主运行多次的结果展示
在这里插入图片描述
我们可以观察到每次的运行结果都不相同,这是什么原因造成的呢?

🎄线程不安全的原因

🚩修改共享数据

上面的线程不安全的代码中, 涉及到多个线程针对同一变量count.count 变量进行修改.

此时这个 coun.count 是一个多个线程都能访问到的 "共享数据“
在这里插入图片描述

count.count 这个变量就是在堆上. 因此可以被多个线程共享访问

要考虑线程安全问题,就需要先考虑Java并发的三大基本特性:原子性、可见性以及有序性

📌原子性

原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行

就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成

再比如下面这个卖票的例子,就不具备原子性
在这里插入图片描述

我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。

那我们应该如何解决这个问题呢?

是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。

有时也把这个现象叫做同步互斥,表示操作是互相排斥的

注意:一条 java 语句不一定是原子的,也不一定只是一条指令

比如我们上述不安全的代码 count ++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU

所以在多线程中,有可能一个线程还没自增完,可能才执行到第二步(进行数据更新),另一个线程就已经读取了值,导致结果错误。那如果我们能保证自增操作是一个原子性的操作,那么就能保证其他线程读取到的一定是自增后的数据。

那么不保证原子性会给多线程带来什么问题?

如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。(这点也和线程的抢占式调度密切相关. 如果线程不是 “抢占” 的, 就算没有原子性, 也问题不大)

📌 可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。简单理解为:一个线程对共享变量值的修改,能够及时地被其他线程看到

若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

可见性就与操作系统内存模型有关系了,这里博主为大家介绍以下Java 内存模型 (JMM)

JMM 为Java虚拟机规范中定义了Java内存模型.

目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果.

在这里插入图片描述
模型说明:

  • 线程之间的共享变量存在 主内存 (Main Memory).

  • 每一个线程都有自己的 “工作内存” (Working Memory) .

  • 当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.

  • 当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存

由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同一个共享变量的 “副本”. 此时修改线程1 的工作内存中的值, 线程2 的工作内存不一定会及时变化.

如下所示:

  1. 初始情况下, 两个线程的工作内存内容一致
    在这里插入图片描述
  2. 一旦线程1 修改了 a 的值, 此时主内存不一定能及时同步. 对应的线程2 的工作内存的 a 的值也不一定能及时同步
    在这里插入图片描述
    这个时候代码中就容易出现问题,就如上述不安全的代码一样

这时候我相信有一部分人就有这样的疑问了

  1. 为啥整这么多内存?

实际并没有这么多 “内存”. 这只是 Java 规范中的一个术语, 是属于 “抽象” 的叫法.
所谓的 “主内存” 才是真正硬件角度的 “内存”. 而所谓的 “工作内存”, 则是指 CPU的寄存器和高速缓存

  1. 为啥要这么麻烦的拷来拷去?

因为 CPU 访问自身寄存器的速度以及高速缓存的速度, 远远超过访问内存的速度(快了 3 - 4 个数量级, 也就是几千倍, 上万倍).

比如某个代码中要连续 10 次读取某个变量的值, 如果 10 次都从内存读, 速度是很慢的. 但是如果只是第一次从内存读, 读到的结果缓存到 CPU 的某个寄存器中, 那么后 9 次读数据就不必直接访问内存了. 效率就大大提高了

然后我们是不是又会有新问题了,既然访问寄存器速度这么快, 还要内存干啥?

答案 :就是一个字: ,寄存器太贵了
在这里插入图片描述

  • 值的一提的是, 快和慢都是相对的. CPU 访问寄存器速度远远快于内存, 但是内存的访问速度又远远快于硬盘.

  • 对应的, CPU 的价格最贵, 内存次之, 硬盘最便宜

📌代码顺序性

程序执行的顺序按照代码的先后顺序执行,在多线程编程时就得考虑这个问题

如果一个线程的话,代码的顺序就为从上到下,依次执行,而当为多线程时就不一样了。

当多个线程同时共享,同一个全局变量或静态变量(即局部变量不会),做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

关于代码顺序性不得不说一个概念:代码重排序

何为代码重排序呢?

比如有段代码是这样的:

  1. 去前台取下 U 盘

  2. 去教室写 10 分钟作业

  3. 去前台取下快递

如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序

编译器对于指令重排序的前提是 “保持逻辑不发生变化”. 这一点在单线程环境下比较容易判断, 但是在多线程环境下就没那么容易了, 多线程的代码执行复杂程度更高, 编译器很难在编译阶段对代码的执行效果进行预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价

重排序是一个比较复杂的话题, 涉及到 CPU 以及编译器的一些底层工作原理, 此处不做过多赘述了

🌲解决之前的线程不安全问题

我们使用关键字synchronized进行加锁操作

 class Count {public int count = 0;//加锁synchronized void increase() {count++;}
}
public class Counter {public static void main(String[] args) throws InterruptedException {final Count count = new Count();//搞两个线程,分别对count进行++操作,每一个线程加50000次Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.increase();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count.count);}
}

我们来看一下代码运行结果:
在这里插入图片描述
我们发现结果正确,说明上述的线程不安全问题解决了

在我们改善后代码里涉及到了一个新的知识synchronized进行加锁操作,该操作博主会在下一篇博客进行详细讲解

⭕总结

关于《【JavaEE初阶】 线程安全》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

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

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

相关文章

深度学习基础知识 学习率调度器的用法解析

深度学习基础知识 学习率调度器的用法解析 1、自定义学习率调度器**&#xff1a;**torch.optim.lr_scheduler.LambdaLR2、正儿八经的模型搭建流程以及学习率调度器的使用设置 1、自定义学习率调度器**&#xff1a;**torch.optim.lr_scheduler.LambdaLR 实验代码&#xff1a; i…

基于Java+SpringBoot+Vue民宿管理系统的设计与实现 前后端分离【Java毕业设计·文档报告·代码讲解·安装调试】

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

【Java】什么是API

API (Application Programming Interface,应用程序编程接口) Java中的API 指的就是 JDK 中提供的各种功能的 Java类&#xff0c;这些类将底层封装起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要学习这些类如何使用即可&#xff0c;我们可以通过帮助文档…

【C语言】阶乘实现

&#x1f389;博客主页&#xff1a;Luo-Kuang-何 &#x1f389;座右铭&#xff1a;一起走向人生巅峰的路上&#x1f601; &#x1f389;学习进度&#xff1a;【C语言】 &#x1f389;博客声明&#xff1a;我将尽我所能&#xff0c;用心写好每一份博客&#xff0c;让更多小伙伴能…

WatchOS开发教程之四: Watch与 iPhone的通信和数据共享

WatchOS 开发教程系列文章: WatchOS开发教程之一: Watch App架构及生命周期 WatchOS开发教程之二: 布局适配和系统Icon设计尺寸 WatchOS开发教程之三: 导航方式和控件详解 WatchOS开发教程之四: Watch与 iPhone的通信和数据共享 WatchOS开发教程之五: 通知功能开发 WatchOS开发…

Kali Linux 秘籍 第九章 无线攻击

第九章 无线攻击 作者&#xff1a;Willie L. Pritchett, David De Smet 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 简介 当今&#xff0c;无线网络随处可见。由于用户四处奔走&#xff0c;插入以太网网线来获取互联网访问的方式非常不方便。无线网络为了使用便利…

python3遍历目录查找文件

一直有一部分软件&#xff0c;他们的主要功能就是方便用户查找本地文件位置。python当然也可以完成这项功能&#xff0c;所以我写了一个简短的代码。 写完发现&#xff0c;python真的是一门简洁的语言啊&#xff01; 我完成这个功能主要就是用了os模块的功能&#xff0c;直接…

WinSvr:在 Windows Server 中启用无线连接

默认情况下,所有 Windows Server 2022/2019/2016/2012R2 版本都禁用无线 (Wi-Fi) 支持。如果将 Wi-Fi 网络适配器(USB 或 PCI)插入运行 Windows Server 的主机,则无法在控制面板中启用它。本文将在这篇简短的说明中展示如何在 Windows Server 上启用无线支持。 注意,在 W…

【运维笔记】Docker 部署Kibana-7.4.0(在线Docker版)

Docker 部署Kibana-7.4.0&#xff08;在线Docker版&#xff09; 一、准备工作&#xff1a; Centos 7.5 安装 Docker-24.0.6 详细步骤&#xff08;避坑版&#xff09;&#xff1a; https://blog.csdn.net/seesun2012/article/details/133674191注意1&#xff1a;本文的命令使用…

短视频账号矩阵系统源码saas===独立部署

前言&#xff1a; 短视频账号矩阵是指在不同的短视频平台上&#xff0c;一个个人或企业所拥有的账号数量和分布情况。由于不同的短视频平台受众人群和内容类型等因素不同&#xff0c;因此拥有更多账号可以在更广泛的受众中传播内容&#xff0c;提高曝光度和流量。短视频账号矩阵…

管理类联考——逻辑——真题篇——按知识分类——第十章 数学相关

第十章 数学相关 第一节 集合 真题&#xff08;2010-53&#xff09;-数学相关-集合-画饼集能力-朴素逻辑 53.参加某国际学术研讨会的 60 名学者中&#xff0c;亚裔学者 31 人&#xff0c;博士 33 人&#xff0c;非亚裔学者中无博士学位的 4 人。根据上述陈述&#xff0c;参…

2017年全国硕士研究生入学统一考试管理类专业学位联考逻辑试题——解析版

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

爬虫小白系列01期: 从李白杜甫,来看爬虫本质 、 浏览器访问网页原理 、 请求头的概念

众所周知&#xff0c;爬虫的本质是&#xff0c;模拟浏览器打开网页&#xff0c;获取网页中我们需要的那部分数据。 那首先我们应该清楚&#xff0c;普通一般浏览器打开网页的流程和原理是怎样的&#xff1f; 根据生活经验&#xff0c;我们使用浏览器打开网页的步骤一般是这样…

神犇营my0001:春晓

本题来源于神犇营 题目: [my0001] 唐代诗人孟浩然所作的《春晓》是一首家喻户晓的诗,但是校园里更流行改编版的《春晓》。 春眠不觉晓, 处处蚊子咬。 夜里嗡嗡声, 脓包知多少。 现在我们要用刚才所学的知识来输出这首诗的前两句。首先在右边的输入C++程序的基本框架…

世界十大名诗

世界十大名诗 时间&#xff1a;2011-01-07 来源&#xff1a;网络 点击&#xff1a;318次 When You Are Old by William Butler Yeats (1865-1939) WHEN you are old and gray and full of sleep, And nodding by the fire, take down this book, And slowly re…

9月火气大,能认真写代码么?

不羡鸳鸯不羡仙&#xff0c;一行代码调半天。原创&#xff1a;小姐姐味道&#xff08;微信公众号ID&#xff1a;xjjdog&#xff09;&#xff0c;欢迎分享&#xff0c;转载请保留出处。 我在风中藏把刀&#xff0c;斩尽世间秋色。 这句注定要流传千古的名诗&#xff0c;是xjjdo…

html语言登黄鹤楼,《中国诗词大会》命题专家方笑一, 揭秘千古名诗《登黄鹤楼》为何格律“不合格”...

楚天都市报10月26日讯(记者舒均 李辉 通讯员王红念 江萌)10月26日上午&#xff0c;华东师范大学古籍研究所教授、央视 《中国诗词大会》命题专家暨现场学术顾问方笑一做客“黄鹤大讲堂”&#xff0c;带来一场《古诗词与天下名楼》品评锦绣诗词的讲座&#xff0c;受到江城上百名…

NLP 自然语言处理实战

前言 自然语言处理 ( Natural Language Processing, NLP) 是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法&#xff0c;用于分析理解机器与人之间的交互&#xff0c;常用的领域有&#xff1a;实体识别、文…

软件行业与就业(导师主讲)

在企业软件应用的整体架构体系中&#xff0c;有一部分被称为中间件&#xff0c;那么什么叫中间件&#xff1f; 中间件&#xff08;Middleware&#xff09;是指位于操作系统和应用程序之间的一层软件层&#xff0c;它提供了一组工具和服务&#xff0c;用于简化和增强企业软件应用…

Docker修改阿里源

在一次安装rtmp推流服务时&#xff0c;总是无法下载源&#xff0c;估计是国外资源下载超时照成的&#xff0c;于是想到修改为国内源。 docker pull alfg/nginx-rtmp Using default tag: latest latest: Pulling from alfg/nginx-rtmp 530afca65e2e: Retrying in 7 seconds c20…