并发编程中的原子性,可见性,有序性问题

news2024/11/20 4:46:28

前言:大家好,我是小威,24届毕业生,在一家满意的公司实习。本篇文章是关于并发编程中出现的原子性,可见性,有序性问题。
本篇文章记录的基础知识,适合在学Java的小白,也适合复习中,面试中的大佬🤩🤩。
如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。
小威在此先感谢各位大佬啦~~🤞🤞
在这里插入图片描述

🏠个人主页:小威要向诸佬学习呀
🧑个人简介:大家好,我是小威,一个想要与大家共同进步的男人😉😉
目前状况🎉:24届毕业生,在一家满意的公司实习👏👏

🎁如果大佬在准备面试,可以使用我找实习前用的刷题神器哦刷题神器点这里哟
💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘

以下正文开始
在这里插入图片描述

文章目录

  • 原子性
  • 可见性
  • 有序性

原子性

在这里插入图片描述

首先看到的这个原子性,对于我们肯定都不陌生,因为在接触数据库的四大特性的时候就遇到过(原子性,一致性,隔离性,持久性)。在数据库中,原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。当然此时说的原子性操作也类似,即线程执行一系列操作,这些操作都会被看着一个不可分割的整体,要么全部执行,要么全部不执行。

原子性是指,CPU在执行一个或多个操作的过程具有原子性,它们是一个不可分割的整体,在执行的过程中不会被中断
举例来说明,几天是个好日子,你的老板看你工作劲头不错,要给你发10000元奖金,而进行转账包含两个操作,老板的银行卡上扣除10000元,你的银行卡上增加10000元,而这两个操作就是一个不可分割的整体,要么两个操作全部执行,要么全部不执行,不能单独出现只有老板银行卡扣除money或者只有你的银行卡增加money的情况。

那我们来深究原子性问题,什么原因导致了原子性问题?
就上面的例子来讲,两个操作是不可分割的,当CPU正在执行老板银行卡扣除money的操作时,此时还没有提交完成,CPU突然切换到你的银行卡增加money操作,这就会出错。

因此,原子性产生的原因还是线程的切换。如果线程正在执行一项操作,发生了线程切换,CPU去执行另一项操作,中断了线程执行的任务,就会产生原子性问题。

回归到更常见的例子,我们在写程序的初始阶段,肯定都写过以下这样的代码:

private int i =0public void add(){
    i++;
}

在单线程情况下,这段代码不会发生的问题,但是在多线程并发的情况下,这段代码可能会发生问题。因为i++,++i等操作并非是原子性操作。这大致上包含三个步骤,1.将变量i从内存中加载到CPU的寄存器中;2.在CPU的寄存器中执行i++或++i的操作;3.将运算的i++的结果写入缓存中。因此在多线程访问时,有可能出现,线程1读取了i的值,线程2也读取了i的值,线程2将i的值进行+1并将i的值写入内存,但是这种情况下,线程1读到的i的值还是原始的那个,因此很容易出现多线程并发上的错误。

再多说一点,对于这种情况,我们可以用加锁和volatile的方法来解决多线程的问题,即:

private volatile int i =0public synchronized void add(){
    i++;
}

但是加锁的方式可能会影响线程执行的效率,因为当线程1拿到锁,线程2无法执行方法,只能等线程1释放锁后再获得CPU的使用权,当线程1执行的时候,线程2处于阻塞状态,所以可能会影响线程的执行效率。

当然在Java中也有一些原子类,他们专门可以保证原子操作,此类位于package java.util.concurrent.atomic包中,这个在后面会详细介绍,大佬感兴趣的话可以先看源码。
在这里插入图片描述

因此做个总结,如果线程在执行的过程中发生线程切换,使得线程暂停当前的任务而去执行其他的任务,可能会发生原子性问题。

在这里插入图片描述

可见性

可见性指的是,在多线程下,一个线程修改了共享变量,其他线程能立即读取到共享变量的最新值。无论共享变量如何变化,其他线程总是能够及时读取到共享变量的最新的值

当线程在串行程序中执行或者线程是在单核CPU情况下执行时,不会出现线程之间的可见性问题。因为在单核CPU中,即使有很多线程,但是一个时刻,CPU只能执行一个线程,也就是说只有一个线程来抢到CPU的资源,来对CPU缓存进行读写操作,在这个线程放弃CPU停止执行任务时,其他线程会对同一个CPU缓存进行读写操作,并且会读取CPU缓存中的最新值,所以不会发生线程的可见性问题。

而在多核CPU下恰恰相反,因为多核CPU下,每个CPU核心都有自己各自的缓存,多个线程在读取主内存的共享变量时,会把主内存中的共享变量复制到线程的私有内存中,每个线程在对数据进行读写操作时,都会直接操作自身工作内存的数据
举例说明,此时线程1和线程2运行在两个不同的CPU核心中,线程1和线程2同时将主内存中的共享变量i复制到自己的CPU缓存中,进行读写操作,但是线程2无法及时读取都线程1中共享变量i的值,线程1也无法及时读取到线程2中共享变量i的值。所以线程1和线程2对共享变量i存在有可见性问题。

因此综上所述,多个线程在多个CPU上运行,会出现可见性问题,所以造成可见性的根本原因就是CPU的缓存机制。在串行程序和单核CPU上不存在可见性问题。

在这里插入图片描述

有序性

有序性,顾名思义,就是有顺序,按顺序执行。在并发编程中亦是如此。有序性是指能够按照编写代码的顺序执行,但是为了提高程序的执行性能和编译性能,计算机和编译器有时候会修改程序的执行顺序。然而,在多线程情况下,编译器对执行顺序的修改可能会造成错误。

下面举例来说明会出现有序性的情况。

在创建单例对象时,使用到了双重检测机制,在并发情况下,可能会出现问题。以下是创建单例对象的方法:

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

假设现在有线程1和线程2,同时执行getInstance()方法获取instance的对象实例,当进行if判断时,instance均为null,此时由于方法加了synchronized锁,只能有一个线程获取锁,另一个线程阻塞,假如线程1获取了锁,创建了对象,执行完成后释放锁,线程2获取锁,发现此时instance不为null,因此不会再创建对象了。

我们在前面也说了new一个对象包括三个步骤:1.为对象分配内存空间;2.初始化对象;3.将对象的引用指向内存空间。

在正常情况下程序是按照顺序执行的,但是如果CPU对对象进行重排序,把第三个步骤排到了第二个步骤的前面,在并发情况下可能就会发生错误。

分析:假设线程1和线程2都进入到了if判断阶段,如果线程1获取了锁,进入到了代码块里,在new对象时,JVM会在堆中为对象找到一块存储空间,并且线程1会将instance的引用指向该内存空间,但是此时并没有为对象进行初始化,因此还是空对象,当线程切换到线程2时,首先会拿到锁,进入到代码块中去,由于instance是空对象,在使用instance时,就可能会出错。

综上,出现此错误的原因就是,编译器修改了创建对象的执行顺序,导致在多线程并发情况下,程序出现了错误。因此出现有序性的根本原因就是编译器修改了程序的执行顺序

本篇文章就分享到这里了,后续还有其他这方面的内容,感谢大佬认真读完支持咯 ~
在这里插入图片描述

文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论🍻
希望能和诸佬们一起努力,今后进入到心仪的公司
再次感谢各位小伙伴儿们的支持🤞

在这里插入图片描述

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

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

相关文章

PyTorch(三)TensorBoard 与 Transforms

文章目录Log一、TensorBoard1. TensorBoard 的安装2. SummaryWriter 的使用① add_scalar() 的使用a. 参数说明b. 函数使用c. 使用 Tensorboard② add_image() 的使用a. 参数说明b. 使用 numpy.array() 对 PIL 图片进行转换c. 使用函数d. 改变 global_step二、Transforms1. Tra…

数据结构 | 时间复杂度与空间复杂度

… 🌳🌲🌱本文已收录至:数据结构 | C语言 更多知识尽在此专栏中! 🎉🎉🎉欢迎点赞、收藏、关注 🎉🎉🎉文章目录🌳前言🌳正…

【C++初阶】类和对象(二)

大家好我是沐曦希💕 类和对象1.类的6个默认成员函数2.构造函数2.1 概念2.2 特性3.析构函数3.1 概念3.2 特性4.拷贝构造函数4.1 概念4.2 特征1.类的6个默认成员函数 空类:类中一个成员都没有 可是空类真的什么都没有吗? 并不是,任…

STM32关于UART的接收方式

STM32的 UART 一般分为定长接收和不定长接收 定长接收: HAL_UART_Receive():只能接收固定长度的数据,如果超过固定长度的数据只能接收对应长度,如果小于固定长度则不会接收 HAL_UART_Receive_IT():中断方式接收,每接收一个字节…

CSS 2 CSS 选择器 - 5 2.8 伪选择器 2.8.1 伪类选择器【根据特定状态选取元素】

CSS 文章目录CSS2 CSS 选择器 - 52.8 伪选择器2.8.1 伪类选择器【根据特定状态选取元素】2 CSS 选择器 - 5 2.8 伪选择器 2.8.1 伪类选择器【根据特定状态选取元素】 【什么是伪类】 伪类用于定义元素的特殊状态。 例如,它可以用于: 设置鼠标悬停在…

如何删除ZIP压缩包的密码?

ZIP是比较常用的压缩文件格式,有时候因为工作需要很多人还会给压缩包设置打开密码。那如果后续不需要密码保护了要如何删除密码呢?密码忘记了还能删除吗? 首先来说说第一种情况,也就是知道密码但后续不需要密码保护,只…

1. 初识Python

1. Pythond 简介 Python 语言由荷兰的 Guido Van Rossum (吉多范罗苏姆, 江湖人称龟叔) 在1989年圣诞节期间为了打发圣诞节的无趣而开发的一个脚本解释语言.Python 源代码遵循 GPL(GNU General Public License)开源协议, 也就是说你可以免费使用和传播它, 而不用担心版权的问…

libusb系列-005-部分API简介

libusb系列-005-部分API简介 文章目录libusb系列-005-部分API简介摘要libusb_initlibusb_open_device_with_vid_pidlibusb_kernel_driver_activelibusb_detach_kernel_driverlibusb_claim_interfacelibusb_release_interfacelibusb_attach_kernel_driverlibusb_closelibusb_exi…

【论文翻译】分布式并发控制中时间戳排序算法与本地计数器同步的改进方法

An Advanced Approach of Local Counter Synchronization to Timestamp Ordering Algorithm in Distributed Concurrency Control DOI目录1 介绍2 时间戳排序算法3 本地计数器同步的一种高级方法3.1 改进更新本地计数器的广播消息方式3.2 减少广播消息中的数据传输费用4 结论参…

时间复杂度与空间复杂度

文章目录1.什么是数据结构2.什么是算法3.如何学好数据结构呢3.1写代码3.2 多去动手画图4.算法效率4.1如何评判一个算法的好与坏呢4.2算法的复杂度5.时间复杂度5.1 概念5.2大O渐进法6常见的时间复杂度6.1常数阶6.2线性阶6.3 对数阶6.4平方阶6.5函数调用6.5.1普通调用6.5.2递归调…

1024程序节|Android框架之一 BRVAH【BaseRecyclerViewAdapterHelper】使用demo

文章目录🍓🍓BRVAH 上部🍄🍓动态图结果展示🍄🍄myAdapter.java【第一个布局适配器】🍄🍄youAdapter.java【第二个布局适配器】🍄🍄MainActivity.java【主活动…

【Android】自制静音App,解决他人手机外放问题

契源 看到一个粉丝留言,吐槽舍友深夜手机外放,打扰别人休息,想设计一款软件阻止舍友行径。于是我就来简单设计一下。 需求实现分析 实际上,我之前有篇博文提到过一个类似的Android APP,主要功能是将手机声音强制开到…

内存函数 memcpy、memmove 的简单模拟实现

一、memcpy 函数 数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。注意是以字节为单位进行拷贝。函数声明如下: 1、参数返回值解析 第二个参数 src:源地址,即你要从哪开始拷贝。 第三个参数 count&#xff1a…

Qt 物联网系统界面开发 “ 2022湖南省大学生物联网应用创新设计竞赛技能赛 ——应用物联网的共享电动自行车 ”

文章目录前言一、实现效果二、程序设计1. 界面背景图设计2. 信号槽设计3. 定时器设计4. 动态曲/折线图的设计5. 摄像头扫码6. 注册设计7. 登录设计8. 巡检人员设计三、综合分析前言 本篇源于 “ 2022 湖南省大学生物联网应用创新设计竞赛技能赛参考样题 ” ——应用物联网的共享…

【git】git ssh 公钥私钥 在 windows和mac 双系统分别如何生成 以及对接各个平台说明

win和mac 双系统分别如何生成 git ssh 一、windows 生成 ssh 公钥私钥 windows版本需要下载git bash:https://gitforwindows.org/ 在 git bash 中输入如下指令: # 创建全局名称(将会在你的git提交作者中显示)git config --glo…

【allegro 17.4软件操作保姆级教程三】布局操作基础二

4精准定位与坐标定位 在设计中经常会有一些器件或结构孔要摆放在指定位置,如果用move命令用鼠标去移则很难定位完全,这时候就需要精准定位。 操作步骤为: 1、点击move命令,在option面板选择器件原点,这时器件就会悬停在…

策略分析中缺失值的处理方法

在日常的策略分析中,经常会碰到分析的变量出现缺失值的情况,如果对这些缺失值视而不见,则会对策略分析的结果造成一定的影响。那么我们如何处理缺失值呢?关注“金科应用研院”,回复“CSDN”领取“风控资料合集” 首先…

本地数据库IndexedDB - 学员管理系统之登录(一)

IndexedDB是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB允许存储大量数据,提供查找接口,还能建立索引。这些都是LocalStorage或Cookie不具备的。就数据库类型而言,IndexedDB不属于关系型数据库(不支…

插入排序图解

七大排序之插入排序 文章目录七大排序之插入排序前言一、直接插入排序1.1 算法图解1.2 算法稳定性1.3 插入排序和选择排序相比到底优在哪?二、折半插入排序总结前言 博主个人社区:开发与算法学习社区 博主个人主页:Killing Vibe的博客 欢迎大…

springboot:实现文件上传下载实时进度条功能【附带源码】

0. 引言 记得刚入行的时候,做了一个文件上传的功能,因为上传时间较久,为了用户友好性,想要添加一个实时进度条,显示进度。奈何当时技术有限,查了许久也没用找到解决方案,最后不了了之。 近来偶…