首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Java并发编程实战 01并发编程的Bug源头

Java并发编程实战 01并发编程的Bug源头

作者头像
Johnson木木
修改于 2020-04-20 07:38:32
修改于 2020-04-20 07:38:32
53500
代码可运行
举报
文章被收录于专栏:猿小俊猿小俊
运行总次数:0
代码可运行

摘要

编写正确的并发程序对我来说是一件极其困难的事情,由于知识不足,只知道synchronized这个修饰符进行同步。 本文为学习极客时间:Java并发编程实战 01的总结,文章取图也是来自于该文章

并发Bug源头

在计算机系统中,程序的执行速度为:CPU > 内存 > I/O设备 ,为了平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都进行了优化:

1.CPU增加了缓存,以均衡和内存的速度差异 2.操作系统增加了进程、线程,已分时复用CPU,以均衡 CPU 与 I/O 设备的速度差异 3.编译程序优化指令执行顺序,使得缓存能够更加合理的利用。

但是这三者导致的问题为:可见性、原子性、有序性

源头之一:CPU缓存导致的可见性问题

一个线程对共享变量的修改,另外一个线程能够立即看到,那么就称为可见性。 现在多核CPU时代中,每颗CPU都有自己的缓存,CPU之间并不会共享缓存;

如线程A从内存读取变量V到CPU-1,操作完成后保存在CPU-1缓存中,还未写到内存中。 此时线程B从内存读取变量V到CPU-2中,而CPU-1缓存中的变量V对线程B是不可见的 当线程A把更新后的变量V写到内存中时,线程B才可以从内存中读取到最新变量V的值

上述过程就是线程A修改变量V后,对线程B不可见,那么就称为可见性问题。

源头之二:线程切换带来的原子性问题

现代的操作系统都是基于线程来调度的,现在提到的“任务切换”都是指“线程切换” Java并发程序都是基于多线程的,自然也会涉及到任务切换,在高级语言中,一条语句可能就需要多条CPU指令完成,例如在代码 count += 1 中,至少需要三条CPU指令。

指令1:把变量 count 从内存加载到CPU的寄存器中 指令2:在寄存器中把变量 count + 1 指令3:把变量 count 写入到内存(缓存机制导致可能写入的是CPU缓存而不是内存)

操作系统做任务切换,可以发生在任何一条CPU指令执行完,所以并不是高级语言中的一条语句,不要被 count += 1 这个操作蒙蔽了双眼。假设count = 0,线程A执行完 指令1 后 ,做任务切换到线程B执行了 指令1、指令2、指令3后,再做任务切换回线程A。我们会发现虽然两个线程都执行了 count += 1 操作。但是得到的结果并不是2,而是1。

如果 count += 1 是一个不可分割的整体,线程的切换可以发生在 count += 1 之前或之后,但是不会发生在中间,就像个原子一样。我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性

源头之三:编译优化带来的有序性问题

有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,可能会改变程序中的语句执行先后顺序。如:a = 1; b = 2;,编译器可能会优化成:b = 2; a = 1。在这个例子中,编译器优化了程序的执行先后顺序,并不影响结果。但是有时候优化后会导致意想不到的Bug。 在单例模式的双重检查创建单例对象中。如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

问题出现在了new Singletion()这行代码,我们以为的执行顺序应该是这样的:

指令1:分配一块内存M 指令2:在内存M中实例化Singleton对象 指令3:instance变量指向内存地址M

但是实际优化后的执行路径确实这样的:

指令1:分配一块内存M 指令2:instance变量指向内存地址M 指令3:在内存M中实例化Singleton对象

这样的话看出来什么问题了吗?当线程A执行完了指令2后,切换到了线程B, 线程B判断到 if (instance != null)。直接返回instance,但是此时的instance还是没有被实例化的啊!所以这时候我们使用instance可能就会触发空指针异常了。如图:

总结

在写并发程序的时候,需要时刻注意可见性、原子性、有序性的问题。在深刻理解这三个问题后,写起并发程序也会少一点Bug啦~。记住了下面这段话:CPU缓存会带来可见性问题、线程切换带来的原子性问题、编译优化带来的有序性问题。

参考文章:极客时间:Java并发编程实战 01 | 可见性、原子性和有序性问题:并发编程Bug的源头

个人博客网址: https://colablog.cn/

如果我的文章帮助到您,可以关注我的微信公众号,第一时间分享文章给您

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-04-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java并发编程(一)---原子性,可见性,有序性
并发编程世界里,由于CPU缓存导致的可见性问题,线程切换导致的原子性问题,以及编译器重排序导致的有序性问题是并发编程Bug的根源。
码农飞哥
2021/08/18
2820
并发编程Bug起源:可见性、有序性和原子性问题
以前古老的DOS操作系统,是单进行的系统。系统每次只能做一件事情,完成了一个任务才能继续下一个任务。每次只能做一件事情,比如在听歌的时候不能打开网页。所有的任务操作都按照串行的方式依次执行。
用户10384376
2023/02/26
2890
并发编程Bug起源:可见性、有序性和原子性问题
java内存模型的理解
可见性定义: 一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性。
大忽悠爱学习
2023/03/06
3620
java内存模型的理解
并发编程问题为什么都很诡异
并发编程对于很多人说都是比较难的,总是出现一些莫名其妙的bug,让我们很是苦恼,那么他到底是难在哪里呢,今天就带大家看看引起并发bug的根源
小土豆Yuki
2020/10/23
4760
并发编程问题为什么都很诡异
Java并发简介(什么是并发)
并发编程中有很多术语概念相近,容易让人混淆。本节内容通过对比分析,力求让读者清晰理解其概念以及差异。
鱼找水需要时间
2023/03/09
9380
Java并发简介(什么是并发)
JAVA并发编程系列(1)synchronized全能王的原理
说到JAVA并发,相信很多人第一印象想到的就是synchronized,然后就是volatile、JUC、CAS、线程池、AQS、阻塞队列等等这些关键字工具类、原理思想。但这些都离不开并发编程的三大特性:原子性、可见性、有序性。
拉丁解牛说技术
2024/09/03
2350
用5000字来聊聊并发编程的源头:可见性、原子性和有序性
工作做螺丝钉,面试造火箭,我想这个是每个程序员比较头疼的事情,但是又有必须经历的流程,我们再聊聊高并发中的原子性、可见性和有序性。
35岁程序员那些事
2022/09/23
3190
Java并发编程系列之一并发理论基础
本系列文章开始Java并发编程的进阶篇的学习,为了初学者对多线程的初步使用有基本概念和掌握,前置知识会对一些基础篇的内容进行介绍,以使初学者能够丝滑入戏。
程序员田同学
2022/03/30
3400
Java并发编程系列之一并发理论基础
不会吧?你还不懂可见性、有序性和原子性吗?
并发编程无论在哪门语言里,都属于高级篇内容,面试中也常常会被问到。想要深入理解并发编程机制确实不是一件容易的事,因为它涉及到计算机底层和操作系统的相关知识,如果对这部分知识不是很清楚可能会导致理解困难。
Java程序猿阿谷
2020/11/24
5600
不会吧?你还不懂可见性、有序性和原子性吗?
并发编程进阶一:从“并发引发的潜在问题”开始
熟悉之处在于:对于一些有一定经验的读者,在面试过程中经常会被问到多线程、高并发的技术解决方案。
浩说编程
2021/08/17
2280
并发编程进阶一:从“并发引发的潜在问题”开始
并发Bug之源有三,请睁大眼睛看清它们
一切设计来源于生活,上一章 学并发编程,透彻理解这三个核心是关键 中有讲过,作为"资本家",你要尽可能的榨取 CPU,内存与 IO 的剩余价值,但三者完成任务的速度相差很大,CPU > 内存 > IO分,CPU 是天,那内存就是地,内存是天,那 IO 就是地,那怎样平衡三者,提升整体速度呢?
用户4172423
2019/09/10
4580
并发Bug之源有三,请睁大眼睛看清它们
JAVA并发编程系列(2)volatile核心原理
上文说到synchronized,JAVA并发编程synchronized全能王的原理,虽然被评为并发全能王,不过用起来也是格外注意,不能搞大力出奇迹那一套,容易出现性能问题。比如synchronized是无法控制阻塞时长,阻塞不可中断问题;以及锁范围,修饰方法或代码块,要精细,仅修饰需要并发控制部分,降低锁粒度。文末再总结一下,synchronized和volatile的区别,先进入正题,聊聊正主:volatile。
拉丁解牛说技术
2024/09/05
1980
Java并发编程
举例:双重检查创建单例对象 可能存在问题,所以要对instance进行volatile语义声明,就可以禁止指令重排序
Java知音
2019/05/13
8420
JAVA并发之多线程引发的问题剖析及如何保证线程安全
为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都做出了贡献,主要体现为:
Java宝典
2020/12/04
2.2K0
Java并发编程实战 03互斥锁 解决原子性问题
Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题
Johnson木木
2020/05/08
5660
Java并发编程实战 03互斥锁 解决原子性问题
Java并发的问题及应对办法
在现今计算机器体系中,涉及性能的主要有CPU、内存、IO三方面,而这三者的速度也是天壤之别,形象之讲,CPU天上一天,内存是地上一年,IO则要地上十年
码农戏码
2022/06/07
1.3K0
Java并发的问题及应对办法
Java 并发编程:volatile的使用及其原理
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁、偏向锁) Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) Java 并发编程:volatile的使用及其原理 一、volatile的作用   在《Java并发编程:核心理论》一文中,我们已经提到过可见性、有序性及原子性问题,通常情况下我们可以通过Synchro
用户2140019
2018/05/18
7400
java并发线程实战(1) 线程安全和机制原理
多个线程同时执行也能工作的代码就是线程安全的代码 如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的
黄规速
2022/04/14
7300
java并发线程实战(1) 线程安全和机制原理
【高并发】解密导致并发问题的第二个幕后黑手——原子性问题
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2020/10/29
4190
【高并发】解密导致并发问题的第二个幕后黑手——原子性问题
Java并发编程实战 02Java如何解决可见性和有序性问题
在上一篇文章[Java并发编程实战 01并发Bug的源头](https://mp.weixin.qq.com/s/QT44HS47l_ir08pCZeFU5Q)当中,讲到了CPU缓存导致可见性、线程切换导致了原子性、编译优化导致了有序性问题。那么这篇文章就先解决其中的可见性和有序性问题,引出了今天的主角:Java内存模型(面试并发的时候会经常考核到)
Johnson木木
2020/04/22
3820
Java并发编程实战 02Java如何解决可见性和有序性问题
推荐阅读
相关推荐
Java并发编程(一)---原子性,可见性,有序性
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档