并发问题的三个根源

并发问题的三个根源

大家好,新的一天,阿清来整理新的知识。并发的相关内容是Java面试的常客。

Java当中,例如synchronized、volatile、lock等关键字都是与并发有关。

那么,并发到底会带来什么样的问题呢?

开门见山,先摆结论,并发的问题主要有以下三类:

  1. 操作原子性问题
  2. 缓存可见一致性问题
  3. 指令序重排问题

我们一个一个来~



01


操作原子性问题



谈起并发中的原子性,数据库的事务也具备原子性,两者的内容比较相似。

原子性:针对定义好的一个或多个操作,在执行时,要么全部都做,要么一个都不做。

举个🌰:

假设银行账户A余额1000元,账户B余额0元,

现在银行中有这样一个业务,账户A向账户B打款1000元。

完成这个业务后,正确的状态:账户A余额0元,账户B余额1000元,

这个业务有两个操作:

  1. 账户A的余额减去1000元。

  2. 账户B的余额加上1000元。

假如栗子鑫负责这个业务。

栗子鑫只做了操作1就停下来了,没有做操作2。

而此时栗子为需要扣除账户B的余额,然而栗子鑫没有更新账户B,此时账户B余额为0元,那么 栗子为 就无法扣除账户B的余额。

因此产生了错误。

这便是因为未能保证原子性而在并发中出现了错误。



02


缓存可见一致性问题




大家要知道,计算机中的指令都是在CPU中执行的,在执行的过程一定会涉及数据的读取与写入。而程序在运行过程中的临时数据都放在了物理内存(主存)中,为了提高程序执行的效率,会在CPU中设置一个告诉缓存。

程序在执行时,首先从主存中读取数据,保存到CPU的高速缓存中,待程序执行完毕,再将数据刷新到主存中。

这样的机制,导致在并发的情况下,缓存中的同一个变量的值可能是不一致的,因此产生错误。

举个🌰:

假设现在有两个线程,

线程A的代码:

int i = 0;
i = 10;

线程B的代码:

int j = i;

假设线程A在为i赋值10后,没有将i的缓存立马刷新到主存中,

此时线程B在执行时,当它读取主存中的i的值,它读到的依然是0,于是在线程B的缓存中i是0。

这便导致了缓存的不一致,即出现了数据的错误。



03


指令序重排问题




有些时候,程序在执行一段代码时,为了提高程序执行的效率。并不会按照代码的顺序来执行,但会保证代码的执行结果与顺序执行的结果一致。

处理器在进行重排序时,会考虑指令之间的数据依赖性,保证相互依赖的语句按照顺序执行。

然而在多线程的情况下,这样的重排序会导致错误。

举个🌰:

我们来看下面这段代码:

// 线程1
int i = 1;             // 语句1
boolean flag = true;   // 语句2

// 线程2
while (!flag) {        // 语句3
   ... 
}
i += 2;                // 语句4

由于语句1与语句2没有数据依赖,处理器可能会对它进行指令序重排。

若先执行了语句2,假设在此时线程1挂起。

线程2开始执行,由于flag已经被赋值,它便会跳出语句3这个循环,执行语句4,

而此时由于语句1没有执行,变量i未被初始化,因此语句4的执行会发生错误。



04


总结



请大家牢记,

并发问题的三个根源:操作原子性、缓存可见一致性、指令序重排

Java针对这些问题,设计了不同的解决方案,包括synchronized、volatile、lock等这些关键字,

后面我再向大家一一介绍~

希望能让你有所收获~

关注六只栗子,面试不迷路。


作者    阿清

编辑   一口栗子  

原文始发于微信公众号(六只栗子):并发问题的三个根源

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88612.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!