摘要:可见性问题还是由CPU的缓存导致的,而缓存导致的可见性问题是导致诸多诡异的并发编程问题的“幕后黑手”之一。

本文分享自华为云社区《【高并发】一文解密诡异并发问题的第一个幕后黑手——可见性问题》,作者:冰 河。

并发编程一直是很让人头疼的问题,因为多线程环境下不太好定位问题,它不像一般的业务代码那样打个断点,debug一下基本就能够定位问题所在。并发编程中,出现的问题往往都是很诡异的,而且大多数情况下,问题也不是每次都会重现的。那么,我们如何才能够更好的解决并发问题呢?这就需要我们了解造成这些问题的“幕后黑手”究竟是什么!

可见性

对于什么是可见性,比较官方的解释就是:一个线程对共享变量的修改,另一个线程能够立刻看到。

说的直白些,就是两个线程共享一个变量,无论哪一个线程修改了这个变量,则另外的一个线程都能够看到上一个线程对这个变量的修改。这里的共享变量,指的是多个线程都能够访问和修改这个变量的值,那么,这个变量就是共享变量。

例如,线程A和线程B,它们都是直接修改主内存中的共享变量,无论是线程A修改了共享变量,还是线程B修改了共享变量,则另一个线程从主内存中读取出来的变量值,一定是修改过的值,这就是线程的可见性。

可见性问题

可见性问题,可以这样理解:一个线程修改了共享变量,另一个线程不能立刻看到,这是由CPU添加了缓存导致的问题。

理解了什么是可见性,再来看可见性问题就比较好理解了。既然可见性是一个线程修改了共享变量后,另一个线程能够立刻看到对共享变量的修改,如果不能立刻看到,这就会产生可见性的问题。

单核CPU不存在可见性问题

理解可见性问题我们还需要注意一点,那就是 在单核CPU上不存在可见性问题。 这是为什么呢?

因为在单核CPU上,无论创建了多少个线程,同一时刻只会有一个线程能够获取到CPU的资源来执行任务,即使这个单核的CPU已经添加了缓存。这些线程都是运行在同一个CPU上,操作的是同一个CPU的缓存,只要其中一个线程修改了共享变量的值,那另外的线程就一定能够访问到修改后的变量值。

多核CPU存在可见性问题

单核CPU由于同一时刻只会有一个线程执行,而每个线程执行的时候操作的都是同一个CPU的缓存,所以,单核CPU不存在可见性问题。但是到了多核CPU上,就会出现可见性问题了。

这是因为在多核CPU上,每个CPU的内核都有自己的缓存。当多个不同的线程运行在不同的CPU内核上时,这些线程操作的是不同的CPU缓存。一个线程对其绑定的CPU的缓存的写操作,对于另外一个线程来说,不一定是可见的,这就造成了线程的可见性问题。

例如,上面的图中,由于CPU是多核的,线程A操作的是CPU-01上的缓存,线程B操作的是CPU-02上的缓存,此时,线程A对变量V的修改对线程B是不可见的,反之亦然。

Java中的可见性问题

使用Java语言编写并发程序时,如果线程使用变量时,会把主内存中的数据复制到线程的私有内存,也就是工作内存中,每个线程读写数据时,都是操作自己的工作内存中的数据。

此时,Java中线程读写共享变量的模型与多核CPU类似,原因是Java并发程序运行在多核CPU上时,线程的私有内存,也就是工作内存就相当于多核CPU中每个CPU内核的缓存了。

由上图,同样可以看出,线程A对共享变量的修改,线程B不一定能够立刻看到,这也就会造成可见性的问题。

代码示例

我们使用一个Java程序来验证多线程的可见性问题,在这个程序中,定义了一个long类型的成员变量count,有一个名称为addCount的方法,这个方法中对count的值进行加1操作。同时,在execute方法中,分别启动两个线程,每个线程调用addCount方法1000次,等待两个线程执行完毕后,返回count的值,代码如下所示。

package io.mykit.concurrent.lab01;/*** @author binghe* @version 1.0.0* @description 测试可见性*/
public class ThreadTest {private long count = 0;private void addCount(){count ++;}public long execute() throws InterruptedException {Thread threadA = new Thread(() -> {for(int i = 0; i < 1000; i++){addCount();}});Thread threadB = new Thread(() -> {for(int i = 0; i < 1000; i++){addCount();}});//启动线程threadA.start();threadB.start();//等待线程执行完成threadA.join();threadB.join();return count;}public static void main(String[] args) throws InterruptedException {ThreadTest threadTest = new ThreadTest();long count = threadTest.execute();System.out.println(count);}
}

我们运行下这个程序,结果如下图所示。

可以看到这个程序的结果是1509,而不是我们期望的2000。这是为什么呢?让我们一起来分析下这个程序。

首先,变量count属于ThreadTest类的成员变量,这个成员变量对于线程A和线程B来说,是一个共享变量。假设线程A和线程B同时执行,它们同时将count=0读取到各自的工作内存中,每个线程第一次执行完count++操作后,同时将count的值写入内存,此时,内存中count的值为1,而不是我们想象的2。而在整个计算的过程中,线程A和线程B都是基于各自工作内存中的count值进行计算。这就导致了最终的count值小于2000。

归根结底:可见性的问题是CPU的缓存导致的。

总结

可见性是一个线程对共享变量的修改,另一个线程能够立刻看到,如果不能立刻看到,就可能会产生可见性问题。在单核CPU上是不存在可见性问题的,可见性问题主要存在于运行在多核CPU上的并发程序。归根结底,可见性问题还是由CPU的缓存导致的,而缓存导致的可见性问题是导致诸多诡异的并发编程问题的“幕后黑手”之一。我们只有深入理解了缓存导致的可见性问题,并在实际工作中时刻注意避免可见性问题,才能更好的编写出高并发程序。

点击关注,第一时间了解华为云新鲜技术~

解密诡异并发问题的幕后黑手:可见性问题相关推荐

  1. 【高并发】一文解密诡异并发问题的第一个幕后黑手——可见性问题

    写在前面 大冰:小菜童鞋,昨天讲解的内容复习了吗? 小菜:复习了,大冰哥. 大冰:那你说说我们昨天都讲了哪些内容呢? 小菜:昨天讲了并发编程的难点,由这些难点引出我们需要了解导致这些问题的" ...

  2. 【高并发】如何解决可见性和有序性问题?这次彻底懂了!

    写在前面 大冰:小菜童鞋,目前,我们把所有可见性问题.原子性问题和有序性问题都介绍完了,感觉自己有啥进步吗? 小菜:大冰哥,通过前面的学习,感觉自己进步确实挺大的,原来学习并发编程包含的知识点这么多, ...

  3. 【并发那些事】可见性问题的万恶之源

    [并发那些事]可见性问题的万恶之源 硬件工程师为均衡 CPU 与 缓存之间的速度差异,特意加的 CPU 缓存,竟然在多核的场景下阴差阳错的成为了并发可见性问题的万恶之源!(本文过长,如果不是特别无聊, ...

  4. volatile关键字——保证并发编程中的可见性、有序性

    文章目录 一.缓存一致性问题 二.并发编程中的三个概念 三.Java线程内存模型 1.原子性 2.可见性 3.有序性 四.深入剖析volatile关键字 1.volatile关键字的两层语义 2.vo ...

  5. 并发编程中的可见性——缓存一致性协议MESI

    一.应用场景展示--多线程计数 1.全局原子操作计数的数据流图 核心问题就是不同CPU如何在同一时刻看到同样的全局变量值. 2.每线程自增计数的数据流图 二.cache原理和实现 1. cache g ...

  6. 【高并发】解密导致并发问题的第二个幕后黑手——原子性问题(文末有福利)

    写在前面 大冰:小菜童鞋,昨天讲解的内容复习了吗? 小菜:复习了大冰哥,昨天的内容干货满满啊,感觉自己收获很大. 大冰:那你说说昨天都讲了哪些内容呢? 小菜:昨天主要讲了线程的可见性和可见性问题.可见 ...

  7. 【高并发】解密导致并发问题的第三个幕后黑手——有序性问题

    写在前面 大冰:小菜童鞋,昨天的内容复习了吗? 小菜:复习了大冰哥,昨天的内容干货满满啊,感觉自己收获很大. 大冰:那你说说昨天都讲了哪些内容呢? 小菜:昨天主要讲了原子性.线程切换和原子性问题,在编 ...

  8. Java并发:线程共享变量可见性原理

    0.线程安全性:线程安全性包括两个方面,①可见性.②原子性. 0.1.线程之间的通信:线程的通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种共享内存和消息传递. (1)在 ...

  9. Java并发:volatile内存可见性和指令重排

    volatile两大作用 1.保证内存可见性 2.防止指令重排 此外需注意volatile并不保证操作的原子性. (一)内存可见性 1 概念 JVM内存模型:主内存和线程独立的工作内存 Java内存模 ...

最新文章

  1. 05章项目:我的租房网
  2. SpringMVC使用及知识点提炼
  3. Windows半透明窗口开发技巧
  4. 【原】两个时间相加的运算符重载实现
  5. Batch Normalization标准化(精)==>一方面可以简化计算过程,一方面经过规范化处理后让数据尽可能保留原始表达能力
  6. Spring框架----IOC的概念和作用之程序的耦合和解耦
  7. .Net 数据缓存浅析
  8. resultset需要关闭吗_你给家里的采暖壁挂炉做保养了吗?
  9. BP算法和RNN_RNN/LSTM BPTT详细推导以及梯度消失问题分析
  10. 使用Patch激活CleanMyPC时报错找不到文件
  11. vue中使用vue-baidu-map 实现点 弹窗 路线 行政区划分
  12. HDU - 3237 Help Bubu (好题)
  13. 简体中文转繁体的python简单实现
  14. 单片微机原理与接口技术——8051汇编指令系统与编程基础(2)数据传送指令
  15. 弘扬中国文化创作发展文学建设事业,间谈小说 “文味”和“接笔 ”的看法...
  16. python获取所有上市公司的加权净资产收益率
  17. 13-4Happy Mid-Autumn Festival
  18. 关于动物识别论文的阅读笔记——青鳉鱼的个体识别和“面部反转效应”
  19. 最近微信上很火的小游戏【壹秒】android版——开发分享
  20. ASP.NET C#药店管理信息系统(含论文)毕业设计【演示视频】

热门文章

  1. java/05/(Swing包)窗体,组件,布局管理器,面板,监听事件
  2. spring mvc 基于表单的认证过程及cookie应用和session管理
  3. Bootstrap 滚动监听插件Scrollspy 的事件
  4. 学习ROS过程中遇到的一些小问题以及解决办法的记录
  5. 轨迹规划当中用到的多项式插值和样条曲线
  6. 已经发车的票还能取出来吗_没想到!火车坐过站,还能免费送回来?方法如下...
  7. 易语言不用oracle客户端_大叔学中文编程之易语言02
  8. matlab randi 函数,MATLAB中的randi函数
  9. python职业发展方向_59秒看懂IT运维的发展方向及职业规划
  10. Ubuntu 安装 cuda 时卡在登录界面(login loop)的解决方案之一