欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > Java:线程安全

Java:线程安全

2024/10/24 4:42:12 来源:https://blog.csdn.net/qrwitu142857/article/details/140892159  浏览:    关键词:Java:线程安全

引子

首先来看一段代码:

    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.这也是多线程代码最困难也最复杂的地方,学习这一块时要有心里预期啊~

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com