线程安全问题重点
Posted 一朵花花
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程安全问题重点相关的知识,希望对你有一定的参考价值。
多线程带来的的风险 - 线程安全
线程安全
线程安全问题是多线程所涉及到的最重要的,也是最复杂的问题
观察线程不安全
public class ThreadDemo14
static class Counter
public int count = 0;
public void increase()
count++;
public static void main(String[] args) throws InterruptedException
Counter counter = new Counter();
Thread t1 = new Thread()
@Override
public void run()
for (int i = 0; i < 50000; i++)
counter.increase();
;
t1.start();
Thread t2 = new Thread()
@Override
public void run()
for (int i = 0; i < 50000; i++)
counter.increase();
;
t2.start();
t1.join();
t2.join();
// 两个线程各自自增5000次,最终预期结果,应该是10w
System.out.println(counter.count);
注意:
运行结果:
多运行几次你会发现,结果并不是10w,而且每次运行的结果都不一样
上述现象:则是线程不安全
线程不安全: 多线程并发执行某个代码时,产生了逻辑上的错误,就是"线程不安全"
线程安全的概念
和线程不安全对应,线程安全就是 多线程并发执行某个代码,没有逻辑上的错误,就是"线程安全"
线程不安全的原因
思考: 为啥会出现上述情况???
原因:
- 线程是抢占式执行的 (线程不安全的万恶之源)
抢占执行:线程之间的调度完全由内核负责,用户代码中感知不到,也无法控制
线程之间谁先执行,谁后执行,谁执行到哪里从CPU上下来,这样的过程都是用户无法控制的,也是无法感知的 - 自增操作不是原子的
每次++,都能拆分成三个步骤:
1.把内存中的数据读取到CPU中 — load
2.在CPU中,把数据+1 — increase
3.把计算结束的数据写回到内存中 — save
当CPU执行到上边三个步骤的任意一个步骤时,都可能被调度器调度走,让给其他线程来执行
画图表示:
上述代码的执行结果在范围 [5w,10w] 之间
极端情况下,
t1 和 t2 每次++ 都是纯并行的,结果就是 5w
t1 和 t2 每次++ 都是纯串行的,结果就是 10w
实际情况,一般不会这么极端,调度过程中有时候是并行,有时候是串行(多少次并行,多少次串行,这个不清楚),因此导致最终的结果是在 [5w,10w] 之间
- 多个线程尝试修改同一个变量
若一个线程修改一个变量,线程安全
若多个线程尝试读取同一个变量,线程安全
若多个线程尝试修改不同的变量,线程安全 - 内存可见性
- 指令重排序
Java 的编译器在编译代码时,会针对指令进行优化 (优化:调整指令的先后顺序,保证原有逻辑不变的情况下, 来提高程序的运行效率)
如何解决线程不安全问题?
1.抢占式执行 — (这个没法解决,操作系统内核解决)
2.自增操作非原子 — (这个有办法,可以给自增操作加上锁) 适用范围最广
3.多个线程同时修改同一个变量 — (这个不一定有办法解决,得看具体的需求)
转下篇:锁
以上是关于线程安全问题重点的主要内容,如果未能解决你的问题,请参考以下文章