[线程]***多线程带来的风险-线程安全问题

news2024/11/15 4:39:33

文章目录

  • 一. 什么是线程安全
  • 二. 线程不安全一个经典的例子
  • 三. 对上述例子的理解
  • 四. 出现线程不安全的原因
    • 1. 线程在操作系统中是随机调度, 抢占式执行的
    • 2. 当前代码中, 多个线程同时修改同一变量
    • 3. 线程针对变量的修改操作, 不是"原子"的
    • 4. 内存可见性问题, 引起线程不安全
    • 5. 指令重排序, 引起线程不安全
  • 五. 解决上述例子问题 --- 上锁
    • 1. 锁
      • 锁的主要操作
      • 锁的主要特性
    • 2. synchronized关键字
    • 3. synchronized的其他写法
      • 1.写在方法中
      • 2. 修饰普通方法
      • 3. 修饰static方法

一. 什么是线程安全

线程, 随机调度, 抢占式执行的, 这样的随机性, 就会使执行顺序, 产生变数, 可能会产生不同的结果, 如果这种结果认为不可接受, 则认为是bug
多线程代码, 引起了bug, 这样的问题, 就是"线程安全问题"
存在"线程安全问题"的代码, 就成为"线程不安全"

二. 线程不安全一个经典的例子

public class Demo13 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " + count);
    }
}

上述代码, t1对count++50000次, t2对count++50000次, 按理来说count应该等于100000, 但是我们运行起来发现:
在这里插入图片描述
多运行几遍发现:
在这里插入图片描述
在这里插入图片描述
这就是非常严重的bug!!
这就是典型的多线程并发引起的问题
如果让俩个线程串行执行, 就没有任何问题的!!
在这里插入图片描述
在这里插入图片描述

三. 对上述例子的理解

CPU执行一个线程, 就是在执行指令, 再引入多线程随机调度, 那么事情就变得很复杂了
上述代码出现bug的原因在于:
在这里插入图片描述

  1. 这一行代码, 其实是3个cpu指令!!!
    1)把内存count中的数值, 读取到cpu的寄存器中 => load
    2)把寄存器中的值+1, 继续保存在寄存器中 =>add
    3)将寄存器中计算后的值, 写回到内存count中 => save
    (后面的名字使我们自己取得, 真实的cpu指令名字可能不是这个)
  2. 多线程的执行, 是随机调度, 是抢占式的运行模式
    某个线程执行指令的时候, 当他执行到任何一个指令的时候, 都有可能被其他的线程把CPU抢占走(操作系统把前一个线程调度走, 后一个线程上位)

结合上述两点, 实际并行开发的时候, 两个线程执行指令的相对顺序就可能会存在多种可能
不同的执行顺序, 得到的结果就可能会存在差异
可能顺序1:
在这里插入图片描述
这个顺序就是可以得到正确结果的顺序
可能顺序2:
在这里插入图片描述
这样的执行顺序就可能会出现问题

四. 出现线程不安全的原因

1. 线程在操作系统中是随机调度, 抢占式执行的

这是线程不安全的罪魁祸首

2. 当前代码中, 多个线程同时修改同一变量

注意关键词: 多个线程, 修改, 同一变量
一个线程修改同一变量
多个线程读取同一变量
多个线程修改不同变量
这些都是没有影响的, 不会出现bug

上述代码, 就是多个线程修改一个count变量

3. 线程针对变量的修改操作, 不是"原子"的

原子: 不可拆分的最小单位
如果某个代码, 对应到一个CPU指令, 就是原子
如果对应到多个, 就不是原子的

像count++这种操作, 就不是原子操作 => 对应三条指令
但是有的操作, 虽然也是修改, 但就是原子操作, 比如针对int / double 进行赋值操作, 就对应一个move指令

4. 内存可见性问题, 引起线程不安全

5. 指令重排序, 引起线程不安全

45后续介绍~~

五. 解决上述例子问题 — 上锁

解决线程安全问题, 最普适的方法, 就是通过一些操作, 把上述"非原子"操作, 打包成一个"原子"操作 ------ 上锁

1. 锁

锁, 本质上也是操作系统内核提供的功能, java(JVM)对这样系统api又进行了封装

锁的主要操作

关于锁, 主要操作两个方面:
1)加锁
t1加上锁后, t2也尝试加锁, 就会阻塞等待(都是系统内核控制, 在java中就可以看到BLOCKED状态)
2)解锁
直到t1解锁了之后, t2才可能加锁成功

锁的主要特性

锁的主要特性: 互斥(也叫锁竞争 / 锁冲突)
一个线程获取到锁之后, 另一个线程也尝试加这个锁, 就会阻塞等待

代码中, 可以创建多个锁, 只有多个线程竞争同一把锁, 才会产生互斥, 针对不同的锁, 则不会产生互斥

2. synchronized关键字

给count++加锁:
1.加锁前, 需要设定一个锁对象
java中, 随便拿一个对象, 都可以作为加锁的对象(这是java独特的设定, 其他语言, 只有极少数待定的对象可以用来加锁)
我们创建一个对象:
在这里插入图片描述
2. 使用synchronized(注意格式)
在这里插入图片描述

  1. synchronized后面跟着( ), ( )里面的内容就是**“锁对象”**
    注意!!!
    锁对象的用途只有一个, 有且只有一个, 就是用来区分两个线程是否是针对同一个对象加锁
    如果是, 就会出现互斥 / 锁竞争 / 锁冲突, 就会引起阻塞等待
    如果不是, 就不会出现锁竞争, 也不会阻塞等待
    和对象具体是是啊类型, 和他里面有啥属性, 有啥方法…都没有关系!!
    我们说"给对象加锁", 也不要误会, 锁对象只有区分是否是同一个锁的作用!!!
    可以理解为: 每个对象只有一把锁, 给t1加锁后, t2就不能再用这个对象的锁了!
  2. synchronized下面跟着{ }
    当进入代码块{ }, 就是给上述锁对象进行加锁操作
    当出了代码块{ }, 就是给上述锁对象进行解锁操作

对同一个对象加锁:

在这里插入图片描述
上述两个线程对同一个对象加锁, 那么就会发生互斥, 此时每次conut++操作都不会被打断, 从而真正可以加到100000
在这里插入图片描述
这样的阻塞, 就使t2的load出现在t1的save后, 强行的构造出了"串行执行"的效果
此时的运行结果:
在这里插入图片描述

对不同的对象加锁:
在这里插入图片描述
此时t1t2对不同的锁对象加锁, 此时就不会产生互斥, 他们之间还是"并发执行"的
运行结果为:
在这里插入图片描述
注意!!!
加锁不是"封装"!!!
t1加上object锁之后,
1)如果t2加的也是object锁, 那么t2是不能够"插队"的, 必须要等到t1解锁,
但是其他线程(没加object锁)是可以和t1抢占cpu的, 并不是将t1封装起来一起运行
2)如果t2加的不是object锁, 那么t2是可以抢占的!!

上述代码, 只有count++操作是互斥的, 是串行执行, 但是线程中的循环操作, 条件判断还是继续并行执行的, 也就是说, 当t1解锁之后, t2是不一定马上能上锁的, t1解锁后, 马上进入下一次循环, 又进行上锁操作, 此时t1 t2 是抢占调度的, 下一次是谁上锁是不一定的

3. synchronized的其他写法

我们将上述代码改写一下:

class Count{
    private int count;
    public void add(){
        count++;
    }
    public int get(){
        return count;
    }
}
public class Demo14 {
    public static void main(String[] args) throws InterruptedException {
        Count count = new Count();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " + count.get());
    }

}

同样也是多个线程对同一变量进行修改操作, 会产生bug
下面我们对add操作进行加锁:

已经有count对象了, 我们就可以直接用count对象进行加锁:
在这里插入图片描述

1.写在方法中

其实我们是对count++进行加锁, 所以可以将锁放在add方法中:
在这里插入图片描述
这里的this就指代count对象

2. 修饰普通方法

此时发现, 加锁的生命周期和方法的声生命周期是一样的, 这个时候, 就可以直接把synchronized写在方法上
在这里插入图片描述
synchronized修饰普通方法, 就相当于是对this加锁

3. 修饰static方法

synchronized也可以修饰static方法, 此时就相当于是对类对象加锁
相当于:
在这里插入图片描述

类对象:
类的属性信息, 包括类的名字, 继承自哪个类, 实现了哪些接口, 提供了哪些方法, 有哪些属性…等等类的全部信息
这些都是我们自己写的, 存在.java源代码中
经过javac编译后, .java => .class字节码文件(但是上述信息仍然存在, 只是变成了二进制)
经过java运行, 就会将.class文件的内容加载到内存中
给后续使用这个类, 提供基础
在内存中保存上述信息的对象, 就是类对象
在java代码中, 可以通过类名.class的方式拿到类对象
一个java进程中, 一个类, 只能有唯一的一个类对象

在这里插入图片描述
如果有多个线程调用func, 则这些线程一定会发生互斥!!!
(因为锁对象是类对象, 只有一个类对象)
在这里插入图片描述
如果多个线程调用add, 就不一定会发生互斥了
(锁对象是this, 指代的对象看你new了几个对象)

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

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

相关文章

使用gradle 移除敏感权限

前言 最近要上架Google Play 但是因为有个敏感权限很容易被拒。 想着把权限依赖的库去掉就行了&#xff0c;但是遇到一个恶心的问题。就是这个权限在Android Studio的Merged Manifest 视图中没有&#xff0c;但是在生成的apk中却包含。这样的就不能通过Android Studio来定位权…

一文搞懂 js 原型和原型链

文章目录 一、前言二、原型2.1 概念2.2 获取原型的方法2.2.1 __proto__获取方式2.2.2 通过构造函数prototype 属性获取2.2.2 ES6 class 通过Object.getPrototypeOf()获取类原型 2.3 通过原型实现继承2.4 原型的作用 三、 原型链四、ES6实现继承五、综述 一、前言 原型和原型链…

计算机网络面试真题总结(七)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 什么是对称加密、非对称加密&#xff1f; 对称加密是一种常用的加…

C语言 之 自定义类型:结构体、结构体内存对齐、修改默认对齐参数 详细说明 可以来看看哟

结构体类型的声明 结构体的声明 struct tag {member-list; //结构体中的成员&#xff0c;可以有多个 }variable-list; //这里是直接创建结构体的变量&#xff0c;但是不一定要在这里声明变量 //不能把后面这个 ; 省略了例如结构体用于描述一个学生&#xff1a; struct Stu…

MySQL内部临时表(Using temporary)案例详解及优化解决方法

目录 前言 一.场景案例 二、什么是内部临时表&#xff1f; 三、哪些场景会使用内部临时表&#xff1f; 四、内部临时表如何存储&#xff1f; 1&#xff09;使用内存 2&#xff09;先使用内存&#xff0c;再转化成磁盘文件 3&#xff09;直接使用磁盘文件 五、如何优化…

Stable Diffusion绘画 | ControlNet应用-IP-Adapter:一致性角色就这么简单

IP-Adapter 更新了全新的模型—FaceID plus V2 版本&#xff0c;同时还支持 SDXL 模型。 FaceID plus V2 版本的优点&#xff1a; 解决任务一致性 一张图生成相似角色 其中&#xff0c;两个 Lora文件 放置在&#xff1a;SD安装目录\models\Lora 两个 bin文件 放置在&#x…

三、IIC 总线协议——1、IIC总线协议介绍

IIC总线协议介绍 1、IIC介绍&#xff1a; Inter Integrated Circuit&#xff0c;同步、串行、半双工通信总线。 2、IIC总线结构图 ① 由时钟线SCL和数据线SDA组成&#xff0c;并且都接上拉电阻&#xff0c;确保总线空闲状态为高电平。 ②总线支持多设备连接&#xff0c;允许…

SSM电动车智能充电桩管理系统 项目源码24481

摘 要 随着社会对环保和可持续发展的关注不断增加&#xff0c;电动车作为清洁能源交通工具受到了广泛关注和推广。然而&#xff0c;电动车充电设施的建设和管理面临着诸多挑战&#xff0c;如充电效率低下、管理繁琐等问题。为解决这些问题&#xff0c;本研究设计开发了一款电…

揭秘无线领夹麦克风五大行业隐秘:音质失真、隐私泄露需警惕!

​无线领夹麦克风是演讲、教学、直播等场合的得力助手&#xff0c;然而市场上品牌众多&#xff0c;产品质量参差不齐&#xff0c;安全隐患层出不穷。作为一名音频设备评测师&#xff0c;我近期入手了多款无线领夹麦克风进行测评&#xff0c;下面就来为大家揭秘无线领夹麦克风行…

Jupyter Notebook详细教程

1、Ipython介绍 介绍 科学计算标准工具集的组成部分 IPython是一个免费、开源的项目&#xff0c;支持Linux、Unix、Mac OS X和Windows平台&#xff0c;其官方网址&#xff1a;Jupyter and the future of IPython — IPython IPython中包括各种组件&#xff0c;其中的两个主要…

无盘设计及其在Allegro中的具体操作

无盘设计的好处有两点&#xff1a; 去掉焊环后&#xff0c;增加了孔与线或是其它孔的间距; 去掉焊环后&#xff0c;铜皮避让的面积更少了&#xff0c;增加了铺铜平面的完整性。 一、确定通孔/过孔焊盘支持去除焊环 只有在封装中勾选了Suppress unconnected internal pads; leg…

AI大模型,互联网的中年革命?人才抢夺白热化,平均工资水平惊呆了……

写在前面 在腾讯股东大会上&#xff0c;CEO马化腾深刻指出&#xff0c;人工智能&#xff08;AI&#xff09;并非仅仅是互联网领域十年一遇的机遇&#xff0c;而是一个具有深远影响的、堪比电力发明的工业革命级别的重大机遇。 本文将包括&#xff1a; 1- 行业概览 2- 大模型…

C++必修:bitset的用法与实现

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C学习 贝蒂的主页&#xff1a;Betty’s blog 1. 位图的引入 首先我们来看一道面试题&#xff1a; 给40亿个不重复的无符号整数…

乌龙?揭露2024国自然被刷真相!

8月24日&#xff0c;国自然正式放榜&#xff0c;并且申请项目批准资助通知、不予资助通知以及专家评审意见也已发送到各位申请人手里。 中了的人兴奋庆祝&#xff0c;没中的人气愤懊恼&#xff0c;一遍又一遍的看着评审意见&#xff0c;甚至会质疑一些评审的意见有些 “不合理…

The Power of Scale for Parameter-Efficient Prompt Tuning

系列论文研读目录 文章目录 系列论文研读目录论文题目含义Abstract1 Introduction2 Prompt Tuning5.6.7.8.9.10. 论文链接 论文题目含义 刻度在参数高效快速调优中的作用 Abstract In this work, we explore “prompt tuning,” a simple yet effective mechanism for lear…

(四)Kafka离线安装 - Kafka下载及安装

Kafka官方下载地址&#xff1a;Apache Kafka 这时候下载安装版本。 我这里的安装目录在 /usr/local/ cd /usr/local/# 创建目录 mkdir kafka cd kafka mkdir kafka_log 把下载的压缩包&#xff0c;放入到/usr/local/kafka/目录下&#xff0c;解压。 # 解压 tar -zxvf kafka…

PDF招生简章如何转二维码?

​随着科技的不断发展&#xff0c;招生报名方式也在不断创新。如今&#xff0c;许多学校和企业都采用PDF招生简章来宣传招生。然而&#xff0c;传统的纸质招生简章存在携带不便、易损坏等问题。为了解决这些问题&#xff0c;将PDF招生简章转换为二维码成为了一种趋势。那你知道…

Laravel邮件发送功能的实现的方法和技巧?

Laravel邮件发信功能如何配置&#xff1f;怎么使用Laravel发信&#xff1f; 在现代Web开发中&#xff0c;邮件发送功能是不可或缺的一部分。Laravel框架以其优雅的语法和强大的功能&#xff0c;成为了许多开发者的首选。AokSend将深入探讨如何在Laravel中实现邮件发送功能&…

让甲方看得见服务器资源降本增效-软件开发不仅考虑开发成本也要重视长期的运维成本

这几天有几个开发者朋友问&#xff0c;用Go语言开发后端真能降低服务器成本吗&#xff1f;本文想分享是低成本一种解决方案&#xff0c;我们不讨论谁是世界上最好的开发语言&#xff0c;所以开发者朋友看到对比语言就不要去挣个高低。GoFly社区今天给大家分享我们这几年用下来真…

餐饮点餐外卖到店小程序系统管理

餐饮业主要以到店就餐和外卖方式/部分细分业快递配送、团餐等满足客户购餐消费需要&#xff0c;互联网时代&#xff0c;尤其是年轻人无论进店与否都追求快捷方便&#xff0c;商家也要提高自身服务效率。 制作餐饮外卖配送/到店/扫码点餐小程序并可在后台开启设置扫码点餐、到店…