大家好,新的一天,阿清来整理新的知识。并发的相关内容是Java面试的常客。
Java当中,例如synchronized、volatile、lock等关键字都是与并发有关。
那么,并发到底会带来什么样的问题呢?
开门见山,先摆结论,并发的问题主要有以下三类:
-
操作原子性问题 -
缓存可见一致性问题 -
指令序重排问题
我们一个一个来~
01
—
操作原子性问题
谈起并发中的原子性,数据库的事务也具备原子性,两者的内容比较相似。
原子性:针对定义好的一个或多个操作,在执行时,要么全部都做,要么一个都不做。
举个🌰:
假设银行账户A余额1000元,账户B余额0元,
现在银行中有这样一个业务,账户A向账户B打款1000元。
完成这个业务后,正确的状态:账户A余额0元,账户B余额1000元,
这个业务有两个操作:
-
账户A的余额减去1000元。
-
账户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