Java并发编程实战 01并发编程的Bug源头
摘要
编写正确的并发程序对我来说是一件极其困难的事情,由于知识不足,只知道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。
在单例模式的双重检查创建单例对象中。如下代码:
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/
如果我的文章帮助到您,可以关注我的微信公众号,第一时间分享文章给您

Java并发编程实战 01并发编程的Bug源头的更多相关文章
- Java并发编程实战笔记—— 并发编程1
1.如何创建并运行java线程 创建一个线程可以继承java的Thread类,或者实现Runnabe接口. public class thread { static class MyThread1 e ...
- Java并发编程实战笔记—— 并发编程2
1.ThreadLocal Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作.因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadL ...
- Java并发编程实战笔记—— 并发编程4
1.同步容器类 同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁保护复合操作. 容器上常见的复合操作包括但不限于:迭代(反复访问数据,直到遍历完容器中所有的元素为止).跳转(根据指定顺 ...
- Java并发编程实战笔记—— 并发编程3
1.实例封闭 class personset{ private final Set<Person> myset = new HashSet<Person>(); public ...
- Java并发编程实战 02Java如何解决可见性和有序性问题
摘要 在上一篇文章当中,讲到了CPU缓存导致可见性.线程切换导致了原子性.编译优化导致了有序性问题.那么这篇文章就先解决其中的可见性和有序性问题,引出了今天的主角:Java内存模型(面试并发的时候会经 ...
- Java并发编程实战 03互斥锁 解决原子性问题
文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和 ...
- Java并发编程实战 04死锁了怎么办?
Java并发编程文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 Java并发编程实战 03互斥锁 解决原子性问题 前提 在第三篇 ...
- Java并发编程实战 05等待-通知机制和活跃性问题
Java并发编程系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 Java并发编程实战 03互斥锁 解决原子性问题 Java并发编程实 ...
- Java并发编程实战——读后感
未完待续. 阅读帮助 本文运用<如何阅读一本书>的学习方法进行学习. P15 表示对于书的第15页. Java并发编程实战简称为并发书或者该书之类的. 熟能生巧,不断地去理解,就像欣赏一部 ...
随机推荐
- C++ 在界面中添加图片
#include <stdio.h> #include <easyx.h> #include <graphics.h> void huayuan() { Begin ...
- 附013.Kubernetes永久存储Rook部署
一 Rook概述 1.1 Ceph简介 Ceph是一种高度可扩展的分布式存储解决方案,提供对象.文件和块存储.在每个存储节点上,将找到Ceph存储对象的文件系统和Ceph OSD(对象存储守护程序)进 ...
- JSTL、请求转发和URL重定向
JSTL 为什么要使用JSTL? 因为在JSP中写JAVA代码很麻烦,而JSTL可以简化在JSp中写JAva代码的流程 如何使用JSTL? 准备工作: ①将JSTL依赖的jar包导入工程的WEB-IN ...
- JAVA--利用HttpClient模拟浏览器登陆请求获取响应的Cookie
在通过java采集网页数据时,我们常常会遇到这样的问题: 站点需要登陆才能访问 而这种网站,一般都会对请求进行账号密码的验证,验证的方式也有多种,需要具体分析. 今天分析其中的一种情况: 站点对登陆密 ...
- Dapper解析嵌套的多层实体类
在作项目的时候,我会将一些不涉及查询的字段,形成JSON统一存放在一个字段中,向下面这样的来建实体类, public class WechatModel { public string wechati ...
- Elasticsearch系列---使用中文分词器
前言 前面的案例使用standard.english分词器,是英文原生的分词器,对中文分词支持不太好.中文作为全球最优美.最复杂的语言,目前中文分词器较多,ik-analyzer.结巴中文分词.THU ...
- 动态规划-Cherry Pickup
2020-02-03 17:46:04 问题描述: 问题求解: 非常好的题目,和two thumb其实非常类似,但是还是有个一点区别,就是本题要求最后要到达(n - 1, n - 1),只有到达了(n ...
- CF codeforces A. New Year Garland【Educational Codeforces Round 79 (Rated for Div. 2)】
A. New Year Garland time limit per test 1 second memory limit per test 256 megabytes input standard ...
- python plt 色卡
https://blog.csdn.net/Strive_For_Future/article/details/100151261 plt 绘图时通常需要各种颜色,还需要去介绍文档找,很麻烦,这里把p ...
- java新时间类
时间 java8以前使用的时间很多方法都已经废弃了,而且不是线程安全的,java8提供了一系列的时间类,这些时间类都是线程安全的 LocalDate.LocalTime.LocalDateTime 这 ...