(四)并发编程带来了哪些问题?

news2024/11/28 12:53:50

(四)并发编程带来了哪些问题?

  • 4.1 引入
  • 4.2 线程安全问题
    • 01、原子性
    • 02、可见性
  • 4.3 活跃性问题
    • 01、死锁
    • 02、活锁
    • 03、饥饿
  • 4.4 性能问题

4.1 引入

在一定场景下,使用多线程会给我们日常工作带来很多的便利,但并不是在任何场景下都适用的。错误地使用多线程不仅不能提高效率,还可能使程序崩溃。

以路上开车为例:在一个单向行驶的道路上,如果每辆车都遵守交通规则行驶,这时候的整体通行是正常的。其中,『单向车道』意味着『一个线程』,『多辆车』意味着『一个线程中的多个 job 任务』。

在这里插入图片描述
如果需要提升车辆的同行效率,一般的做法就是扩展车道,对应着程序来说就是『加线程池』,增加线程数。这样,在同一时间内,通行的车辆数就远远大于了单车道。
在这里插入图片描述
然而,车道一旦多起来,也就意味着『加塞』的场景可能就会越来越多。出现碰撞后也会影响整条马路的通行效率。这么一对比,『多车道』确实可能比『单车道』要慢。
在这里插入图片描述
防止汽车频繁变道加塞情况的发生,可以采取在车道间增加『护栏』,以保证多个车道互不影响。

在程序中该如何实现呢?在程序中多线程遇到的问题归纳起来就是三类:线程安全问题活跃性问题性能问题

4.2 线程安全问题

有时候我们会发现,明明在单线程环境中正常运行的代码,在多线程环境中可能会出现意料之外的结果。其实,这就是我们常听到的线程不安全。那么,到底什么是线程不安全呢?

01、原子性

银行转账例子:账户 A 向账户 B 转账 1000 元,那么必然包括 2 个操作:从账户 A 减去 1000 元,往账户 B 加 1000 元,两个操作都成功才意味着一次转账最终成功。
在这里插入图片描述
试想一个比较极端的现象:如果这两个操作并不具备原子性,从账户 A 扣了 1000 块钱之后,操作突然终止了,账户 B 并没有增加 1000 块钱,那么就有问题了。
在这里插入图片描述
银行转账这个例子有两个步骤,出现了意外之后导致转账失败,说明没有原子性。

原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就不执行。

原子操作:即不会被线程调度机制打断的操作,没有上下文切换。

举个例子:

i = 0; // 操作1
i++; // 操作2
i = j; // 操作3
i = i + 1; // 操作4

上面的四个操作中,有哪些操作是原子性的?来分析一下:

  1. 操作1:对基本数据类型变量的赋值是原子操作;
  2. 操作2:包含三个操作,读取 i 的值,将 i 加 1,将值赋给 i;
  3. 操作3:读取 j 的值,将 j 的值赋给 i;
  4. 操作4:包含三个操作,读取 i 的值,将 i 加 1,将值赋给 i。

在单线程环境下,上面四个操作都不会出现问题,但是在多线程环境下,如果不通过加锁操作,往往可能得到意料之外的值。

在 Java 语言中通过可以使用 synchronize 或者 lock 来保证原子性。

02、可见性

class Test {
  int i = 50;
  int j = 0;
  
  public void update() {
    // 线程1执行
    i = 100;
  }
  
  public int get() {
    // 线程2执行
    j = i;
    return j;
  }
}

线程 1 执行 update() 方法将 i 赋值为 100,一般情况下线程 1 会在自己的工作内存中完成赋值操作,却没有及时将新值刷新到主内存中。

这时线程 2 执行 get() 方法,首先会从主内存中读取 i 的值,然后加载到自己的工作内存中,这个时候读取到 i 的值是 50,再将 50 赋值给 j,最后返回 j 的值就是 50 了。

原本期望返回的是 100,结果返回 50,这就是可见性问题,线程 1 对变量进行了修改,线程 2 没有立即看到 i 的新值。

可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

在这里插入图片描述
如图所示,每个线程都有属于自己的工作内存,工作内存和主内存之间需要通过 store 和 load 等进行交互。

为了解决多线程的可见性问题,Java 语言提供了 volatile 关键字。当一个共享变量被 volatile 关键字修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通共享变量不能保证可见性,因为变量被修改后什么时候回到主存是不确定的,另外一个线程读的可能就是旧值。

当然,Java 的锁机制比如 synchronize 和 lock 也是可以保证可见性的,加锁可以保证在同一时刻只有一个线程在执行同步代码块,释放锁之前将变量刷回至主存,这样也就保证了可见性。

4.3 活跃性问题

为了解决可见性问题,我们可以采取加锁方式解决,但是如果加锁使用不当也容易引入其他问题,比如死锁。

那活跃性是什么呢?活跃性是指某件正确的事情最终会发生,当某个操作无法继续下去的时候,就会发生活跃性问题。

01、死锁

死锁是指多个线程因为环形的等待锁的关系而永远的阻塞下去。
在这里插入图片描述

02、活锁

死锁是两个线程都在等待对方释放锁导致阻塞。而活锁中的线程并没有阻塞。当多个线程都在运行并且修改各自的状态,而其他线程彼此依赖这个状态,导致任何一个线程都无法继续执行,只能重复着自身的动作和修改自身的状态,这种场景就是发生了活锁。
在这里插入图片描述
如上图所示,两辆车迎面相走并互相谦让,但又有同时走到了一个方向。如果一直这样重复着避让,这两个人就发生了活锁。

03、饥饿

如果一个线程没有其他异常却迟迟不能继续运行,那基本就是处于饥饿状态了。

常见有几种场景:

  1. 高优先级的线程一直在运行消耗 CPU,所有的低优先级线程一直处于等待状态;
  2. 一些线程被永久堵塞在一个等待进入同步块的状态,而其他线程总是能在它之前持续地对该同步块进行访问。

有一个非常经典的饥饿问题就是哲学家用餐问题,有五个哲学家在用餐,每个人必须要同时拿两把叉子才可以开始就餐,如果哲学家 1 和哲学家 3 同时开始就餐,那哲学家 2、4、5 就得饿肚子处于等待状态了:

在这里插入图片描述

4.4 性能问题

前面提到了线程安全、死锁、活锁这些问题会影响多线程的执行过程,如果这些都没有发生,多线程并发一定比单线程串行执行的快吗?答案是不一定的,因为多线程有创建线程和线程上下文切换的开销

创建线程是直接向系统申请资源的,对操作系统来说,创建一个线程的代价是十分昂贵的,需要给它分配内存、列入调度等。

线程创建完成之后,还会遇到线程的上下文切换。
在这里插入图片描述
CPU 是很宝贵的资源,速度也非常快,为了保证雨露均沾,通常为给不同的线程分配时间片,当 CPU 从执行一个线程切换到执行另一个线程时,CPU 需要保存当前线程的本地数据、程序指针等状态,并加载下一个要执行的线程的本地数据、程序指针等,这个开关被称为上下文切换

一般减少上下文切换的方法有:

  1. 无锁并发编程:可以参照 concurrentHashMap 锁分段的思想,不同的线程处理不同的数据,这样在多线程竞争的条件下,可以减少上下文切换的时间。
  2. CAS 算法,利用 Atomic 下使用 CAS 算法来更新数据,使用了乐观锁,可以有效的减少一部分不必要的锁竞争带来的上下文切换。
  3. 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多的线程,这样会造成大量的线程都处于等待状态。
  4. 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

总之,多线程用好了可以让程序的效率成倍提升,用不好可能比单线程还要慢。

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

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

相关文章

记一次数据库迁移(迁移数据)

book,由于之前建表没注意字符集的问题,导致之前写入的数据出现乱码。现在要将之前的数据和现在数据的字符集一致,不出现乱码情况,将字符集为 latin1 已有记录的数据转成 utf8,并且已经存在的记录不乱码。 操作步骤: 建…

SPI接口调试

本文记录了复旦微fmql45t900 SPI裸核和linux系统下spi接口的调试步骤。 问题描述: 复旦微fmql45t900 SPI接口片选信号无法拉低控制。 原因分析: 为了排除硬件问题,创建spi裸核测试工程进行单步调试,spi发送数据时用示波器可以正…

FPGA USB FX2 图片发送试验 驱动CY7C68013A实现 提供2套工程源码和技术支持

目录 1、前言2、我这儿已有的 FPGA USB 通信方案3、CY7C68013A芯片解读和硬件设计FX2 简介SlaveFIFO模式及其配置 4、工程详细设计方案输入测试图片的处理PC上位机发送测试图片图像接收与缓存图像输出显示 5、vivado工程1--LCD输出显示6、vivado工程1--LCD输出显示7、上板调试验…

vue数组深层赋值

一、问题 使用vue开发的项目,有一个页面的data中定义了两个数组,需要把其中一个数组赋值给另一个数组的其中一个对象的一个属性(有点拗口),如图所示: 二、错误赋值 直接使用opts:this.print_name&#xf…

el-button与i标签实现区域切换效果

题记&#xff1a;我们工作中需要实现内容区域切换放大缩小的效果&#xff0c;其实道理很简单&#xff0c;给事件给样式即可。 #el-button与i标签实现区域切换效果 ##图片展示&#xff1a; ##代码实现 ###template部分 <div class"right"><el-button type&…

【SQL应知应会】行列转换(三)• Oracle版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 行列转换 • Oracle版 oracle的行列转换前言1.数据…

Perhaps you are running on a JRE rather than a JDK?

我记得我遇到过好多次 mvn clean package的时候报错&#xff1b; 最后检查时这里的路径配置错了

蓝牙耳机品牌排行榜前十名!2023年超全蓝牙耳机合集!

虽然称不上发烧友&#xff0c;但近年来用过的蓝牙耳机少说也有几十款了。这篇文章以近年来市面上的热销且评价都比较高的十款蓝牙耳机为主要推荐对象&#xff0c;来给大家做一期有关于蓝牙耳机的实测体验以及提供相关的选购思路&#xff0c;希望能为大家的选择出一份力。 第一…

# TSWIKI 0.2 版本

TSWIKI 0.2 版本 TSWIKI 0.2 版本经过这段时间的改进&#xff0c;增加了搜索&#xff0c;版本变更查看等功能&#xff0c;基本开发完成。功能方面已经与 gollum 类似。 功能改进说明 1、GIT 版本变更查看功能 实现 git 版本变更查看功能, 在 markdown 文档查看界面&#xf…

day 44 完全背包

完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一不同…

LLVM介绍

LLVM项目是什么 工业级编译技术集合 优化器和代码生成器llvm-gcc和Clang前端MSIL和.NET虚拟机开源项目 行业组织、研究团体和个人 LLVM愿景 主要使命&#xff1a;构建一套模块化的编译器组件&#xff1a; 减少构建特定编译器的时间和成本组件可以在不同的编译器之间共享使用…

OpenCat:一个基于Arduino和Raspberry Pi的开源四足机器人宠物框架

一个深圳项目&#xff0c;OpenCat&#xff1a;一个基于Arduino和Raspberry Pi的开源四足机器人宠物框架。 项目灵感来自于到波士顿动力公司Big Dog和Spot Mini的启发。 OpenCat提供了一个基础的开源平台&#xff0c;可以创建令人惊奇的可编程步态、运动四足机器人。它可以进行复…

什么是孪生视频,孪生视频是什么意思,她的关键技术,及应用场景案例

视频孪生&#xff0c;即视频数字孪生。是集三维地理信息系统、视频虚实融合、数字孪生、人工智能、时空位置智能、地球空间网格编码、知识图谱于一体的多学科、多维度、多尺度的综合技术应用&#xff0c;是对数字孪生的创新升级。 什么是视频孪生&#xff0c;视频孪生的关键技术…

c++ 之decay简单介绍

简介 std::decay是C获取类型的一种方式&#xff0c;它定义在<type_traits>头文件中。 #include <type_traits>模型 template< class T > using decay_t typename decay<T>::type; //(since C14)使用示例 template<typename T> static void …

关于流程图svg线条流动的效果-jq

获取所有的SVG并加上相应的样式 var svgElement $(svg.jtk-connector);console.info(svgElement,svgElement)var pathElement svgElement.find(path:first);pathElement.attr(class, connector-line);pathElement.attr(fill, transparent);pathElement.attr(stroke, #1890ff)…

vue3项目打开本地pdf文件实现方法

vue3项目打开本地pdf文件实现方法 效果图引入pdf插件pdf页面封装pdf存放目录结语 效果图 引入pdf插件 注意一定要这个版本&#xff0c;不然会报错key.split(...).at is not a function npm install pdfjs-dist2.12.313pdf页面封装 <template><div class"pdf-co…

饮料市场京东销售数据分析(京东大数据)

近日&#xff0c;关于“阿斯巴甜可能是致癌物”的话题持续发酵&#xff0c;众所周知阿斯巴甜是常见的人工甜味剂之一&#xff0c;并被广泛应用于无糖可乐以及一些饮料产品中&#xff0c;而这一话题引起人们的巨大恐慌。 阿斯巴甜致癌与否尚未定论&#xff0c;但这一言论也引得…

【学习笔记-QGIS】 QGIS从零快速上手

原文感谢作者才华横溢吴道简 安装文章参考&#xff1a;https://zhuanlan.zhihu.com/p/370633306 一、下载安装 QGIS免费开源&#xff0c;中文界面&#xff0c;下载地址&#xff1a;https://download.qgis.org/downloads/ 三、配置中文环境 开始——QGIS 3.18——QGIS Deskto…

SpringBoot2+Vue2实战(十)权限管理之动态菜单、动态路由生成

一、父子菜单实现 新建数据库表 sys_menu sys_role 实体类 Role import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable;import l…

Numpy速通笔记

Numpy可以高效处理大数组的数据&#xff0c;因为&#xff1a; Numpy在一个连续的内存块中存储数据&#xff0c;独立于其他Python内置对象。Numpy是C写的&#xff0c;有优化&#xff0c;比Python内置序列使用的内存少可以在整个数组上进行复杂运算&#xff0c;不需要for循环 下…