线程安全问题【重点】

线程安全

线程安全问题是多线程所涉及到的最重要的,也是最复杂的问题

观察线程不安全

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.多个线程同时修改同一个变量 — (这个不一定有办法解决,得看具体的需求)

转下篇:

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>