文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

面试突击:线程安全问题是如何产生的?

2024-12-02 04:11

关注

举个例子来说,比如银行只有张三一个人来办理业务,这种情况在程序中就叫做单线程执行,而单线程执行是没有问题的,也就是线程安全的。但突然有一天来了很多人同时办理业务,这种情况就叫做多线程执行。如果所有人都一起争抢着办理业务,很有可能会导致错误,而这种错误就叫非线程安全。如果每个人都能有序排队办理业务,且工作人员不会操作失误,我们就把这种情况称之为线程安全的。

问题演示

接下来我们演示一下,程序中非线程安全的示例。我们先创建一个变量 number 等于 0,然后开启线程 1 执行 100 万次 number++ 操作,同时再开启线程 2 执行 100 万次 number-- 操作,等待线程 1 和线程 2 都执行完,正确的结果 number 应该还是 0,但不加干预的多线程执行结果却与预期的正确结果不一致,如下代码所示:

public class ThreadSafeTest {
// 全局变量
private static int number = 0;
// 循环次数(100W)
private static final int COUNT = 1_000_000;

public static void main(String[] args) throws InterruptedException {
// 线程1:执行 100W 次 number+1 操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number++;
}
});
t1.start();

// 线程2:执行 100W 次 number-1 操作
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number--;
}
});
t2.start();

// 等待线程 1 和线程 2,执行完,打印 number 最终的结果
t1.join();
t2.join();
System.out.println("number 最终结果:" + number);
}
}

以上程序的执行结果如下图所示:

从上述执行结果可以看出,number 变量最终的结果并不是 0,和我们预期的正确结果是不相符的,这就是多线程中的线程安全问题。

产生原因

导致线程安全问题的因素有以下 5 个:

接下来我们分别来看这 5 个因素的具体含义。

1.多线程抢占式执行

导致线程安全问题的第一大因素就是多线程抢占式执行,想象一下,如果是单线程执行,或者是多线程有序执行,那就不会出现混乱的情况了,不出现混乱的情况,自然就不会出现非线程安全的问题了。

2.多线程同时修改同一个变量

如果是多线程同时修改不同的变量(每个线程只修改自己的变量),也是不会出现非线程安全的问题了,比如以下代码,线程 1 修改 number1 变量,而线程 2 修改 number2 变量,最终两个线程执行完之后的结果如下:

public class ThreadSafe {
// 全局变量
private static int number = 0;
// 循环次数(100W)
private static final int COUNT = 1_000_000;
// 线程 1 操作的变量 number1
private static int number1 = 0;
// 线程 2 操作的变量 number2
private static int number2 = 0;

public static void main(String[] args) throws InterruptedException {
// 线程1:执行 100W 次 number+1 操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number1++;
}
});
t1.start();

// 线程2:执行 100W 次 number-1 操作
Thread t2 = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
number2--;
}
});
t2.start();

// 等待线程 1 和线程 2,执行完,打印 number 最终的结果
t1.join();
t2.join();
number = number1 + number2;
System.out.println("number=number1+number2 最终结果:" + number);
}
}

以上程序的执行结果如下图所示:

从上述结果可以看出,多线程只要不是同时修改同一个变量,也不会出现线程安全问题。

3.非原子性操作

原子性操作是指操作不能再被分隔就叫原子性操作。比如人类吸气或者是呼气这个动作,它是一瞬间一次性完成的,你不可能先吸一半(气),停下来玩会手机,再吸一半(气),这种操作就是原子性操作。而非原子性操作是我现在要去睡觉,但睡觉之前要先上床,再拉被子,再躺下、再入睡等一系列的操作综合在一起组成的,这就是非原子性操作。非原子性操作是有可以被分隔和打断的,比如要上床之前,发现时间还在,先刷个剧、刷会手机、再玩会游戏,甚至是再吃点小烧烤等等,所以非原子性操作有很多不确定性,而这些不确定性就会造成线程安全问题问题。像 i++ 和 i-- 这种操作就是非原子的,它在 +1 或 -1 之前,先要查询原变量的值,并不是一次性完成的,所以就会导致线程安全问题。比如以下操作流程:

操作步骤

线程1

线程2

T1

读取到 number=1,准备执行 number-1 的操作,但还没有执行,时间片就用完了。


T2


读取到 number=1,并且执行 number+1 操作,将 number 修改成了 2。

T3

恢复执行,因为之前已经读取了 number=1,所以直接执行 -1 操作,将 number 变成了 0。


以上就是一个经典的错误,number 原本等于 1,线程 1 进行 -1 操作,而线程 2 进行加 1,最终的结果 number 应该还等于 1 才对,但通过上面的执行,number 最终被修改成了 0,这就是非原子性导致的问题。

4.内存可见性问题

在 Java 编程中内存分为两种类型:工作内存和主内存,而工作内存使用的是 CPU 寄存器实现的,而主内存是指电脑中的内存,我们知道 CPU 寄存器的操作速度是远大于内存的操作速度的,它们的性能差异如下图所示:

那这和线程安全有什么关系呢?这是因为在 Java 语言中,为了提高程序的执行速度,所以在操作变量时,会将变量从主内存中复制一份到工作内存,而主内存是所有线程共用的,工作内存是每个线程私有的,这就会导致一个线程已经把主内存中的公共变量修改了,而另一个线程不知道,依旧使用自己工作内存中的变量,这样就导致了问题的产生,也就导致了线程安全问题。

5.指令重排序

指令重排序是指 Java 程序为了提高程序的执行速度,所以会对一下操作进行合并和优化的操作。比如说,张三要去图书馆还书,舍友又让张三帮忙借书,那么程序的执行思维是,张三先去图书馆把自己的书还了,再去一趟图书馆帮舍友把书借回来。而指令重排序之后,把两次执行合并了,张三带着自己的书去图书馆把书先还了,再帮舍友把书借出来,整个流程就执行完了,这是正常情况下的指令重排序的好处。但是指令重排序也有“副作用”,而“副作用”是发生在多线程执行中的,还是以张三借书和帮舍友还书为例,如果张三是一件事做完再做另一件事是没有问题的(也就是单线程执行是没有问题的),但如果是多线程执行,就是两件事由多个人混合着做,比如张三在图书馆遇到了自己的多个同学,于是就把任务分派给多个人一起执行,有人借了几本书、有人借了还了几本书、有人再借了几本书、有人再借了还了几本书,执行的很混乱没有明确的目标,到最后悲剧就发生了,这就是在指令重排序带来的线程安全问题。

总结

线程安全是指某个方法或某段代码,在多线程中能够正确的执行,不会出现数据不一致或数据污染的情况,反之则为线程安全问题。简单来说所谓的非线程安全是指:在多线程中,程序的执行结果和预期的正确结果不一致的问题。而造成线程安全问题的因素有 5 个:多线程抢占式执行、多线程同时修改同一个变量、非原子性操作、内存可见性和指令重排序。

来源:Java面试真题解析内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯