多线程带来的风险——线程安全

Posted 无赖H4

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程带来的风险——线程安全相关的知识,希望对你有一定的参考价值。

线程安全

  1. 多线程代码中往往表现出很强的随机性
    其主要原因是:线程调度所导致的

一个程序是线程安全的概念:程序的运行结果100%符合程序开发的期望

为什么会出现线程不安全的情况

  1. 没有保证原子性
  2. 存在内存可见性问题
  3. 由于代码重排序所带来的的问题

原子性

高级语言中的一条语句往往对应的是很多条指令,例如:n++
指令:

  1. 把n这个变量对应的内存中的数据,加载到寄存器中 ( LOAD)
  2. 在寄存器中,利用ALU完成自增(+1)操作 (INC)
  3. 把寄存器中的数据,保存回n这个变量对应的内存中(STORE)

不保证原子性会给多线程带来什么问题:

如果一个线程正在对一个变量操作,突然时间片耗尽,另一个线程开始操作,结果可能是错误的。

原子性:一组操作,不能被打断,或者打断之后没有副作用。

常见的操作是原子的

  1. a == b ,不是原子的(读b的值,赋值给a)
  2. byte/short/char/int/boolean/float a = 1; 是原子的
  3. long/double a = 1L; 不是原子的(高32位赋值,低32位赋值)
  4. 引用 a = null; 是原子的
  5. 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的缓存——> 工作存储
真实的内存 ——> 主存储

代码重排序

  1. CPU最终执行代码(指令)的顺序并不一定和书写顺序完全一致,过程中,代码被交换过顺序了。例如:小时候的统筹安排时间的问题,找最短时间

a = 1;b = 2; c = 3; 可能重排序后是: a = 1; c = 3; b = 2;
2. 在很多时候,我们的书写顺序并不是效率最高的顺序。
3. 那些可以帮我们重排序: 编译器(javac)、虚拟机(JVM)、CPU
4. JVM进行重排序的底线是什么?

单线程的情况下,无论如何进行重排序,程序的运行结果不能出现变化。
多线程情况下,不做保证

那些场景下会存在线程安全

共享的数据——共享资源

  1. 线程和线程之间,完全没有对同一个数据的共享操作。——天生线程安全
  2. 线程和线程之间,有数据共享,但都是只读操作。——天生线程安全
  3. 线程和线程之间,有数据共享,并且至少有一个线程在做修改操作时——需要考虑线程安全爆出

常见的一些类

程序的线程安全:运行结果100%符合预期
类/对象是线程安全:使用这些类/对象的使用者们,无需额外做线程安全的考虑

不是线程安全的:
StringBuilder、ArrayList、LinkedList、PriorityQueue、HashMap、TreeMap、Connection

线程安全:(但是基本被淘汰)
StringBuffer、Vector、Stack、HashTable

以上是关于多线程带来的风险——线程安全的主要内容,如果未能解决你的问题,请参考以下文章

java并发编程:多线程带来的安全风险问题,详细解释!

3线程带来的风险

3线程带来的风险

线程安全问题重点

线程安全问题重点

Java(高阶)——线程安全