Java并发原理
网上有不计其数的并发编程文章,甚至有不计其数的书来介绍这个主题。你为什么要花10分钟时间来读完这篇文章呢?我给的答案:“他们全是废话。”,我觉得这个主题用10分钟就可以说完,根本不要用花这么长时间,也不用去折腾Java内存模型之类的东西。
我只讲原理,不会告诉你怎么用Java的并发库,这是java doc干的事情
理解Java并发原理或者其他语言的并发(没错,这篇文章是“跨语言”的!!!还这么短,你说牛逼不牛逼)只需要记住理解两个东西:
CPU访问存储的方式——多级存储;
CPU执行指令的方式——乱序
首先回忆我们大学的一门课程——《计算机组成原理》也许你的记忆里只有:“呃,你要说xx进制转换成xx进制吗?”。没关系我帮你回忆一下:
有一节课讲多级存储,说计算机最快的存储是CPU里面的Cache,其次是内存,最后是硬盘,最次的是外部存储(比如光盘之类的)。
还有一节课讲的是CPU流水线,乱序执行、分支预测,说CPU考虑性能问题会把几个没有数据关联的指令打乱顺序执行。
怎么样?有印象了吗?(什么?没读过大学?那我觉得你有必要读一下大学的课程——即便你不想混文凭)。
多级存储
我们来看一个“无聊的”Java例子(例子没有任何意义,会枯燥一些,耐着性质你读懂了可以超脱了)
程序定义了一个线程,线程会不停的判断stop标志位,如果为真则循环累加i。然后我们在主线程里面修改stop为true。期望线程在进行2秒之后停止。
如果运行这个程序我们得到的结果是——程序永远不会停止。主线程里面修改的变量在testThread里面并没有发生改变。
解释这个程序就用到了“多级存储”,在x86架构的CPU中对数据的的访问都是经过寄存器,如果数据在内存中CPU会先加载到寄存器然后在读取;写入的时候CPU只写入到寄存器,在“适当的时候”数据会被回写到内存中。画个图:
操作系统把我们程序中的主进程和testThread调度到不同的CPU,testThread(CPU1)访问stop的时候数据被复制到Cache中然后读取;主进程(CPU2)访问stop的时候数据被复制到Cache中然后读取,赋值的时候会写入到Cache中。所以CPU2修改的值并不会立马被CPU1看到,这取决于:
CPU2是不是写回到内存中;
CPU1的Cache是不是被“淘汰”重新从内存中加载数据;
第一条比较容易满足,因为Cache必定会回写到内存中(只不过不是实时写入);第二条看起来比较困难,唯一的解决办法是我们访问stop变量的时候每次都从内存加载而不是通过Cache。在Java中实现这个功能的关键字是volatile。
public static volatile boolean stop = false;
这样程序就可以“正常”执行了。需要注意,volatile只保证“好吧,我不用Cache”,无法保证原子性(比如赋值操作被拆分为多个CPU指令,那么其他进程可能看到的是一个“中间结果”)。所以volatile其实是一种低效、不安全的并发处理方式。(不使用Cache效率低,无法保证原子性所以不安全)。
流水线,乱序执行、分支预测
代码比上一个更加枯燥,忍耐一下:
我定义了4个变量,两个线程,然后分别启动两个线程,等待线程执行完之后输出x,y的值。同志们可以猜猜结果是多少。(注释后面的标号代表语句编号)
没错,根本没有“正确”答案。我这里有四种答案:
结果:x=0, y=1;执行顺序:1, 2, 3, 4
结果:x=1, y=0;执行顺序:3, 4, 1, 2
结果:x=1, y=1;执行顺序:1, 3, 2, 4
结果:x=0, y=0;执行顺序:2, 4, 1, 3
(前面三种执行结果你多执行几次都会出现,后面的理论是存在。但是我没有执行出来,单颗CPU更容易出现这样的结果)
这就是并发的本质,你的代码不会按照你写顺序执行。前三个很容解释,两个线程可能会被“交替”执行,让人困惑的是第四个结果,解释这个就必须用到“流水线,乱序执行、分支预测”。
CPU内部有多个执行单元(如果是多个CPU那就更多执行单元了),为了提高吞吐量,它会采用流水线同时执行多条指令;为了优化程序执行的效率适应流水线,CPU会分析指令的依赖关系把可以并行执行的指令并行执行。
在one线程中,a=1和y=b是没有任何依赖关系的,所以可能y=b会被先执行,a=1则后执行。同样的道理other线程中也是如此。
总结
没错,存储访问引起的不一致性+CPU为了提高效率引入的并行机制就是并发程序设计的困难,这两个问题结合在一起就是“Memory barrier”(内存屏障、内存栅栏),这不是Java独有的,在任何编程语言中都会存在这个问题,除非你的CPU不是多级存储、没有流水线(这还是CPU吗?)。
原文作者:写程序的康德 原文链接:https://mp.weixin.qq.com/s?__biz=MzIxMjAzMDA1MQ==&mid=2648945421&idx=1&sn=98b9c1b5fa004ec49d1f9f0484185c38#rd
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
ThinkSNS Plus PHP开发概述
Plus (读音:[plʌs],全称:ThinkSNS+[θɪŋk es en es plʌs],是 ThinkSNS 系列产品一个重要版本,其软件识别名称为Plus即+) 是一个基于Latest Laravel框架进行开发的一个功能强大、易于开发和强拓展的社交系统。与其他开源社交程序不同的是 Plus 拥有多年社交系统经验,不仅易于上手,还便于应用拓展。另一方面,程序采用 PHP 7 严格模式,从根本上尽量避免弱级错误的产生。同时因为从零开始选择较好的带有较好 ORM 的原因,Plus 允许你更具你的需求使用不同数据库。 如果你想深入学习 Plus,我们为你准备了大量教程级文档。哪怕你不会 Laravel 框架,也能让你入门框架基础,并胜任 Plus 应用开发。 如果你是有经验的 PHPer,那么你可以了解现代流行框架差异,Laravel 就是现代留下框架的佼佼者之一。 #PHP 环境要求 重点 你可能还没有很好的 Liunx 知识,没关系,后面的教程会拟定你是零基础的前提下教学,但是下面的环境要求限制,你需要重点记忆,这是程序能否运行的关键所在! #PHP 版本 您的 ...
- 下一篇
解决图片 防盗链
js版解决方案 var url = 'https://mmbiz.qpic.cn/mmbiz_jpg/TAoksPVlXMI7dQPxiaUbAHvyJ19iaG9b2Ueh53iaqTsn6F8O3m63zcBibgNpujM1HNeCKX99vOov72LpHuqs92SMlg/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1'; var path = showImg(url); console.log(path); document.getElementById('img').innerHTML = path; function showImg(url) { var frameid = 'frameimg' + Math.random(); window.img = '<img id="img" src=\'' + url + '?' + Math.random() + '\' /><script>window.onload = function() { parent.document.getEl...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- 设置Eclipse缩进为4个空格,增强代码规范
- Mario游戏-低调大师作品
- MySQL8.0.19开启GTID主从同步CentOS8
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS8编译安装MySQL8.0.19
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池