多线程带来的风险——线程安全
Posted 无赖H4
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程带来的风险——线程安全相关的知识,希望对你有一定的参考价值。
线程安全
- 多线程代码中往往表现出很强的随机性
其主要原因是:线程调度所导致的
一个程序是线程安全的概念:程序的运行结果100%符合程序开发的期望
为什么会出现线程不安全的情况
- 没有保证原子性
- 存在内存可见性问题
- 由于代码重排序所带来的的问题
原子性
高级语言中的一条语句往往对应的是很多条指令,例如:n++
指令:
- 把n这个变量对应的内存中的数据,加载到寄存器中 ( LOAD)
- 在寄存器中,利用ALU完成自增(+1)操作 (INC)
- 把寄存器中的数据,保存回n这个变量对应的内存中(STORE)
不保证原子性会给多线程带来什么问题:
如果一个线程正在对一个变量操作,突然时间片耗尽,另一个线程开始操作,结果可能是错误的。
原子性:一组操作,不能被打断,或者打断之后没有副作用。
常见的操作是原子的
- a == b ,不是原子的(读b的值,赋值给a)
- byte/short/char/int/boolean/float a = 1; 是原子的
- long/double a = 1L; 不是原子的(高32位赋值,低32位赋值)
- 引用 a = null; 是原子的
- a == b 32位+引用 是原子的;64位不是原子
常见的不是原子的操作
read-write: a = b
check-update: if(a == …) { a = …;}
内存可见性
在真实的计算机中,会存在一个高速缓存器(Cache),它的作用是提前加载数据,方便CPU的读取
例如:每次进行操作都需要执行,LOAD/STORE 等,比较耗时,当出现存储器后,只需要最开始加载,最后写入即可。
问题:
当出现多个CPU,操作同一个数据,数据因为没有及时的更新,所以会出现错误。
内存可见性:由于CPU缓存技术的出现,导致在多个CPU的场景下,可能出现数据不一致的情况——内存中的数据相互之间不见了。
JMM
JMM(Java Memory Model)Java内存模型
存储被分为两类:主存储+工作存储
主存储只有一份。工作存储每个线程各一份。JVM可以根据具体场景决定什么时候在同步回主存储中。
CPU ——> 线程
属于CPU的缓存——> 工作存储
真实的内存 ——> 主存储
代码重排序
- CPU最终执行代码(指令)的顺序并不一定和书写顺序完全一致,过程中,代码被交换过顺序了。例如:小时候的统筹安排时间的问题,找最短时间
a = 1;b = 2; c = 3; 可能重排序后是: a = 1; c = 3; b = 2;
2. 在很多时候,我们的书写顺序并不是效率最高的顺序。
3. 那些可以帮我们重排序: 编译器(javac)、虚拟机(JVM)、CPU
4. JVM进行重排序的底线是什么?
在单线程的情况下,无论如何进行重排序,程序的运行结果不能出现变化。
多线程情况下,不做保证
那些场景下会存在线程安全
共享的数据——共享资源
- 线程和线程之间,完全没有对同一个数据的共享操作。——天生线程安全
- 线程和线程之间,有数据共享,但都是只读操作。——天生线程安全
- 线程和线程之间,有数据共享,并且至少有一个线程在做修改操作时——需要考虑线程安全爆出
常见的一些类
程序的线程安全:运行结果100%符合预期
类/对象是线程安全:使用这些类/对象的使用者们,无需额外做线程安全的考虑
不是线程安全的:
StringBuilder、ArrayList、LinkedList、PriorityQueue、HashMap、TreeMap、Connection
线程安全:(但是基本被淘汰)
StringBuffer、Vector、Stack、HashTable
以上是关于多线程带来的风险——线程安全的主要内容,如果未能解决你的问题,请参考以下文章