Java并发编程实战 代码bug,Java并发编程实战(1)- 并发程序的bug源头

概述

并发编程一般属于编程进阶部分的知识,它会涉及到很多底层知识,包括操作系统。

编写正确的并发程序是一件很困难的事情,由并发导致的bug,有时很难排查或者重现,这需要我们理解并发的本质,深入分析Bug的源头。

并发程序问题的源头

为了提升系统性能,在过去几十年中,我们一直在不断的提升硬件的设计,包括CPU、内存以及I/O设备,但存在一个主要矛盾:三者之间速度有很大差异,CPU最快,内存其次,I/O设备最慢。

我们编写的程序,在运行过程中,上述三者都会使用到,在这种情况下,速度慢的内存和I/O设备就会成为瓶颈,为了解决这个问题,计算机体系结构、操作系统和编译程序做了如下改进:

CPU增加了缓存,以均衡与内存的速度差异。

操作系统增加了进程、线程以及分时复用CPU,从而均衡CPU与I/O设备的速度差异。

编译程序优化指令执行次序,使得缓存能够得到更加合理的利用。

并发程序的问题根源也基本来源于上述改进:

缓存引发的可见性问题

线程切换引发的原子性问题

编译优化引发的有序性问题

接下来我们分别展开描述。

缓存引发的可见性问题

什么是可见性?

可见性是说一个线程对共享变量的修改,另外一个线程能够立刻看到。

可见性问题是由CPU缓存引起的,它是在CPU变为多核后才出现的,单核CPU并不会存在可见性问题。

我们可以参考下面的示意图。

223cfc13643a

如图所示,当有2个线程同时访问内存中的变量x时,2个线程运行在不同的CPU上,每个CPU缓存都会保存变量x,线程运行时,会通过CPU缓存来操作x,那么当线程1进行操作后,线程2并不会立刻得到更新后的x,从而引发了问题。

我们来看下面的代码示例,它显示了对同一个变量使用多个线程进行加操作,最后判断变量值是否符合预期。

public class ConcurrencyAddDemo {

private long count = 0;

private void add() {

int index = 0;

while (index < 10000) {

count = count + 1;

index++;

}

}

private void reset() {

this.count = 0;

}

private void addTest() throws InterruptedException {

List threads = new ArrayList();

for (int i = 0; i < 6; i++) {

threads.add(new Thread(() -> {

this.add();

}));

}

for (Thread thread : threads) {

thread.start();

}

for (Thread thread : threads) {

thread.join();

}

threads.clear();

System.out.println(String.format("Count is %s", count));

}

public static void main(String[] args) throws InterruptedException {

ConcurrencyAddDemo demoObj = new ConcurrencyAddDemo();

for (int i = 0; i < 10; i++) {

demoObj.addTest();

demoObj.reset();

}

}

}

程序运行的结果如下。

Count is 18020

Count is 18857

Count is 16902

Count is 16295

Count is 54453

Count is 59475

Count is 56772

Count is 37376

Count is 60000

Count is 60000

我们可以看到,并不是每次返回的结果都是60000。

线程切换引发的原子性问题

什么是原子性?

一个或者多个操作在CPU执行的过程中不被中断的特性,被称为原子性。原子性可以保证操作执行的中间状态,对外是不可见的。

CPU可以保证的原子操作是在CPU指令级别的,并不是高级语言的操作符,而高级语言中的一个操作,可能会包含多个CPU指令。

以上述代码中的count = count + 1为例,它至少包含了三条CPU指令:

指令1:首先需要把变量count从内存加载到CPU寄存器。

指令2:在寄存器中执行+1操作。

指令3:将结果进行保存,这里可能会保存在CPU缓存,也可能保存在内存中。

上述指令执行过程中,可能会产生”线程切换“,如果多个线程同时执行相同的语句,那么因为线程切换,就会导致结果不是我们期望的。

原子性问题并不只在多核CPU中存在,在单核CPU中也是存在的。

编译优化引发的有序性问题

什么是有序性?

有序性是指程序按照代码的先后顺序执行。

编译器为了优化性能,有时候会改变程序中语句的先后顺序,一般情况下,这并不会影响程序的最终结果,但有时也会引发意想不到的问题。

我们以典型的单例模式为例进行说明,示例代码如下。

public class SingletonDemo {

private static SingletonDemo instance;

public static SingletonDemo getInstance() {

if (instance == null) {

synchronized(SingletonDemo.class) {

if (instance == null) {

instance = new SingletonDemo();

}

}

}

return instance;

}

}

一般情况下,假设有两个线程 A、B 同时调用 getInstance() 方法,他们会同时发现 instance == null ,于是同时对 Singleton.class 加锁,此时 JVM 保证只有一个线程能够加锁成功(假设是线程 A),另外一个线程则会处于等待状态(假设是线程 B);线程 A 会创建一个 Singleton 实例,之后释放锁,锁释放后,线程 B 被唤醒,线程 B 再次尝试加锁,此时是可以加锁成功的,加锁成功后,线程 B 检查 instance == null 时会发现,已经创建过 Singleton 实例了,所以线程 B 不会再创建一个 Singleton 实例。

但是,如果我们仔细分析getInstance()方法中的new操作,会发现它包含以下几步:

分配一块内存M。

在内存M上初始化SingletonDemo对象。

将M的地址赋值给instance变量。

但编译器可能会做一些优化,变成下面的样子:

分配一块内存M。

将M的地址赋值给instance变量。

在内存M上初始化SingletonDemo对象。

这样很可能导致线程 B获取instance之后,在instance初始化没有完全结束的情况下,调用它的方法,从而引发空指针异常。

上述是我们常见的并发程序的bug源头,只要我们能够深刻理解可见性、原子性和有序性在并发场景下的原理,很多并发bug就很容易理解了。

参考资料:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/429020.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ajax小结

转载于:https://www.cnblogs.com/infernoyy/p/7250548.html

app推送以及提示音java,springboot 整合 Jpush 极光推送

产品简介&#xff1a;JPush 是经过考验的大规模 App 推送平台&#xff0c;每天推送消息数超过 5 亿条。 开发者集成 SDK 后&#xff0c;可以通过调用 API 推送消息。同时&#xff0c;JPush 提供可视化的 web 端控制台发送通知&#xff0c;统计分析推送效果。 JPush 全面支持 An…

Lydsy2017年4月月赛 抵制克苏恩

Description小Q同学现在沉迷炉石传说不能自拔。他发现一张名为克苏恩的牌很不公平。如果你不玩炉石传说&#xff0c;不必担心&#xff0c;小Q同学会告诉你所有相关的细节。炉石传说是这样的一个游戏&#xff0c;每个玩家拥有一个30 点血量的英雄&#xff0c;并且可以用牌召唤至…

怎样学习(3):迭代学习,精益求精

古人云「十年寒窗无人问。一举成名天下知」&#xff0c;这是中国古代为数不多的读书人的真实写照。大多数读书人仅仅有十年寒窗&#xff0c;却不见得成名。 在软件开发领域有瀑布模式的软件project方法论。它将软开发的几个过程「需求分析&#xff0c;概要设计&#xff0c;具体…

matlab宏参赛,MATLAB杯无人机大赛 | 决赛通知!

原标题&#xff1a;MATLAB杯无人机大赛 | 决赛通知&#xff01;重磅消息——决赛通知&#xff01;经过近5个多月的准备&#xff0c;MATLAB杯无人机比赛即将迎来精彩的决赛&#xff0c;来自全国10强的参赛队伍&#xff0c;齐聚羊城广州&#xff0c;美丽的中山大学&#xff0c;进…

php表格js特效,JavaScript表格隔行变色和Tab标签页特效示例【附jQuery版】

本文实例讲述了JavaScript表格隔行变色和Tab标签页特效。分享给大家供大家参考&#xff0c;具体如下&#xff1a;最近一直在看JavaScript知识&#xff0c;偶尔也穿插一点Jquery&#xff0c;感觉Jquery用起来真爽&#xff0c;减少了很多的代码量&#xff0c;而且学习也不是很高。…

java实现gdal栅格矢量化,《GDAL源码剖析与开发指南》一一1.5 GDAL源码目录

本节书摘来自异步社区出版社《GDAL源码剖析与开发指南》一书中的第1章&#xff0c;第1.5节&#xff0c;作者&#xff1a;李民录 更多章节内容可以访问云栖社区“异步社区”公众号查看。1.5 GDAL源码目录GDAL源码剖析与开发指南下载的GDAL源代码压缩包目录如图1-2所示&#xff0…

netlify支持php吗,hexo netlify 搭建简易博客

npm install hexo-cli -ghexo init blogcd blognpm installhexo server将本地文件夹推送到github修改主题git clone https://github.com/jangdelong/hexo-theme-xups.git themes/xups themes/xups修改yml配置文件重新hexo server自己的博客sleepy-poincare-e0ca11.netlify.c…

jps、jstack、jmap、jhat、jstat、hprof使用详解

https://my.oschina.net/feichexia/blog/196575#comment-list A、 jps(Java Virtual Machine Process Status Tool) jps主要用来输出JVM中运行的进程状态信息。语法格式如下&#xff1a; 如果不指定hostid就默认为当前主机或服务器。 命令行参数选项说明如下&#xff1a;…

oracle数据库日期格式的运算,Oracle时间类型date,timestamp时间差计算

Oracle的时间类型有两种date和timestamp. date精确到秒,timestamp精确到毫秒.1.计算date类型的时间差可以先把年,月,日,小时,分,秒用to_char函数拆分出来,再用to_number函数转换成数值类型.有了这些单独分开的时间就好办了.就再一个个的去减,记得考虑单位换算就行.比如都转换…

url去除掉一个参数php,php怎样去掉url中的参数_后端开发

php去掉url中的参数的要领是&#xff1a;能够经由过程trim()函数来完成。该函数能够删除字符串中的指定字符&#xff0c;并返回已修正的字符串。细致使用要领如&#xff1a;【trim($url,"?");trim($url,"#");】。相干函数引见&#xff1a;(引荐教程&#…

C++之类的静态成员变量和静态成员函数

static静态成员函数 在类中。static 除了声明静态成员变量&#xff0c;还能够声明静态成员函数。普通成员函数能够訪问全部成员变量。而静态成员函数仅仅能訪问静态成员变量。我们知道。当调用一个对象的成员函数&#xff08;非静态成员函数&#xff09;时&#xff0c;系统会把…

使用VS Code开发.Net Core 2.0 MVC Web应用程序教程之一

好吧&#xff0c;现在我们假设你已经安装好了VS Code开发工具、.Net Core 2.0预览版的SDK dotnet-sdk-2.0.0&#xff08;注意自己的操作系统&#xff09;&#xff0c;并且已经为VS Code安装好了C#扩展&#xff08;在VS Code的扩展菜单中输入OmniSharp&#xff0c;安装扩展即可&…

WPF--TextBlock的ToolTip附加属性

大家可能在项目中&#xff0c;有的时候&#xff0c;由于显示的内容过长&#xff0c;所以&#xff0c;需要显示一部分内容&#xff0c;然后后面用省略号&#xff0c;把鼠标放上去&#xff0c;会显示出来全部的内容。 作为一个LowB程序员的我&#xff0c;第一反应是SubString截取…

Laravel框架一:原理机制篇

转载自http://www.cnblogs.com/XiongMaoMengNan/p/6644892.htmlLaravel作为在国内国外都颇为流行的PHP框架&#xff0c;风格优雅&#xff0c;其拥有自己的一些特点。 一. 请求周期 Laravel 采用了单一入口模式&#xff0c;应用的所有请求入口都是 public/index.php 文件。 注册…

Linux高频命令汇总,Linux高频命令

8种机械键盘轴体对比本人程序员&#xff0c;要买一个写代码的键盘&#xff0c;请问红轴和茶轴怎么选&#xff1f;findfind path [options] params作用&#xff1a;在指定目录下查找文件1234find / -name "target.java" #在根目录下查找target.java文件find -name &qu…

linux系统时间函数,Linux时间时区详解与常用时间函数

时间与时区整个地球分为二十四时区&#xff0c;每个时区都有自己的本地时间。UTC时间 与 GMT时间我们可以认为格林威治时间就是时间协调时间(GMT UTC)&#xff0c;格林威治时间和UTC时间都用秒数来计算的。UTC时间与本地时间UTC 时区差 本地时间时区差东为正&#xff0c;西为…

linux awr 日志,Linux平台生成awr报告

1&#xff0e;使用Oracle用户登录应用服务器所使用的数据库所在的服务器# su –oracle2&#xff0e;输入env命令&#xff0c;查询出ORACLE_HOME 目录3&#xff0e;然后进入$ORACLE_HOME/rdbms/admin目录&#xff0c;查询awr语句(此步骤可以忽略)此处可以直接省略2&#xff0c;3…

HDU 1159 Common Subsequence 动态规划

2017-08-06 15:41:04 writer&#xff1a;pprp 刚开始学dp&#xff0c;集训的讲的很难&#xff0c;但是还是得自己看&#xff0c;从简单到难&#xff0c;慢慢来&#xff08;如果哪里有错误欢迎各位大佬指正&#xff09; 题意如下&#xff1a; 给两个字符串&#xff0c;找到其中大…

【Xmail】使用Xmail搭建局域网邮件服务器

下载地址&#xff1a; http://www.xmailserver.org/xmail-1.27.win32bin.zip&#xff0c;当前最新版本 1.27。 解压文件&#xff1a;xmail-1.27.win32bin.zip 1、把其中的 MailRoot 目录拷贝到 C:\&#xff1b; 2、把 C:\xmail-1.27.win32bin\xmail-1.27 目录下的所有文件&am…