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

news/2024/5/20 7:24:02 标签: 1024程序员节, 多线程, 并发编程, JUC

前言:大家好,我是小威,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.niftyadmin.cn/n/642.html

相关文章

Java 计算相关

Java语言健壮性 Java和C最大的不同在于Java采用的指针模型可以消除重写内存和损坏数据的可能性。 Java计算精度问题 由于Java中的double类型使用64位存储一个数值,而有些处理器使用80位的浮点寄存器,但最终结果都要被截断位64位,这就可能会…

《数据结构》(六)八大排序(上)

生活中大家从小到大处处可见排队,但是排队有哪些快速的方法你了解吗? 八大排序排序的基本概念插入排序直接插入排序基本思想代码直接插入排序总结希尔排序基本思想代码希尔排序总结选择排序直接选择排序基本思想:代码直接选择排序总结堆排序堆…

24、25届如何准备实习?

24、25届如何准备实习? 正如标题所言,在这个寒冬我们24、25届的同学该如何准备实习需要的一些东西呢? 很巧的是本人也是24届的菜鸡一枚嘿嘿,不能说自己是超级大牛的那种,但是本人之前找实习的经历可以分享给大家&…

通道分离与合并、彩色图转换为灰度图、二值化

文章目录图像基础重要的函数图像基本知识图像基础通道分离与合并彩色图转换为灰度图二值化图像的加减乘除图像基础 矩阵分辨率8位整型图像浮点数图像 现在简单介绍下二值化、灰度图以及真彩色和假彩色 图像的二值化,就是将图像上的像素点的灰度值设置为0或255&am…

【C++笔试强训】第十一天

🎇C笔试强训 博客主页:一起去看日落吗分享博主的C刷题日常,大家一起学习博主的能力有限,出现错误希望大家不吝赐教分享给大家一句我很喜欢的话:夜色难免微凉,前方必有曙光 🌞。 💦 &…

DASCTF X GFCTF 2022十月 Misc

文章目录Misc滴滴图poi?qoi!ez_xxdeasy_dotsansicdockermiscMisc 滴滴图 jpg(png)后面有压缩包,压缩包的文件尾有段密码 是解压此压缩包的,而压缩包一共有两个,都解压看过了内容相同 \u0074\u0068\u0069\u0073\u005…

School assignment

目录 一、常用控制类汇编指令 二、编程实现统计寄存器AX中“1”和“0”的个数 三、编程实现从键盘输入10个1位整数 四、编程实现从键盘输入两个10进制的2位整数的和 五、编写程序练习直接、间接、相对、基址变址寻址 一、常用控制类汇编指令 MOV 传送字或字节. MOVSX 先…

大数据项目之电商数仓、用户行为日志、服务器和JDK准备、模拟数据

文章目录3. 用户行为日志3.4 服务器和JDK准备3.4.1 服务器准备3.4.2 编写集群分发脚本xsync3.4.3 SSH无密登录配置3.4.4 JDK准备3.4.5 环境变量配置说明3.5 模拟数据3.5.1 使用说明3.5.1.1 将application.yml、gmall2020-mock-log-2021-10-10.jar、path.json、logback.xml上传到…