JavaEE: 线程安全

10 篇文章 0 订阅

引子

首先来看一段代码:

    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });

        t1.start();
        t2.start();
        
        t1.join();
        t2.join();

        System.out.println(count);
    }

运行结果:
第一次
在这里插入图片描述
第二次
在这里插入图片描述
第三次
在这里插入图片描述
我们发现,以上代码有bug,我们的预期是10w,显然上述的结果不是我们想要的.

初次分析

为什么会出现上述的问题呢?
我们来分析一下.
如果我们把代码修改成这样:

	t1.start();
    t1.join();
        
    t2.start();
    t2.join();

运行结果就没问题了.
这个写法本质上相当于,t1先执行,t1执行完了,t2再执行,此时t1和t2是串行执行的.

而刚刚的代码:

	t1.start();
    t2.start();
        
    t1.join();
    t2.join();

这个写法,t1和t2是并发执行的.

由此我们可以知道这是由于多个线程并发执行引起的bug.
这样的bug就称为"线程安全问题"或者叫做"线程不安全".

再次分析

那这个bug到底是怎么出现的呢,换句话说,为什么会出现线程不安全呢?
线程不安全是一个综合性的问题.竖起耳朵仔细听啦~

代码中的 count++; 操作,其实在CPU视角来看,是3个指令:

  1. 把内存中的数据读取到CPU寄存器里
  2. 把CPU寄存器里的数据 +1
  3. 把寄存器的值,写回内存
    为了方便叙述,给上面三步分别起名为 load , add , save.
    (由于不同架构的CPU有不同的指令集,所以对于这三个操作,不同的CPU里对应指令的名称肯定是不同的~)

由于CPU是"抢占式执行,随机调度"的,也就是说CPU在调度执行线程的时候,不定啥时候就会把线程给切换走.
(指令是CPU执行的最基本单位,它不会执行一半就被调走)

但是由于count++ 是三个指令,可能会出现CPU执行了其中的1个指令或者两个指令或者三个指令就被调度走的情况(无法预测).

基于上面的情况,两个线程同时对count进行++,就容易出现bug
正常情况如下
在这里插入图片描述
上述的执行顺序,只是一种可能的调度顺序.由于调度过程是"随机"的,因此就会产生很多其他的执行顺序~

比如:

在这里插入图片描述

甚至:

在这里插入图片描述
相信你看完下面的图,肯定就会理解了~
在这里插入图片描述
通过上述分析,我们发现对于多线程代码来说,最大的困难就在于线程在操作系统中是"随机调度,抢占式执行"的,这简直就是"罪魁祸首,万恶之源"啊.


总结

最后总结一下为什么最开始的代码有bug吧:

  1. 线程在操作系统中是"随机调度,抢占式执行"的
  2. 多个线程,同时修改了同一个变量
  3. 修改操作,不是"原子的"

怎么规避呢?
第一条原因我们一般不能改,但是我们可以改第二条,比如说改成使用一个线程,或者使用多个线程修改不同的变量等等.

我们写多线程代码需要在所有可能的线程调度的顺序下,确保每一种情况都没有bug.这也是多线程代码最困难也最复杂的地方,学习这一块时要有心里预期啊~

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月临水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值