当前位置: 首页 > news >正文

多线程下race condition问题

       这个问题的讨论来自内部的一个关于“多线程环境下使用Hashmap的安全问题”的讨论,HashMap多线程的问题之前已经提过一次,见之前的blog.本篇文章主要讨论多线程下race condition的问题。以下内容部分引用自内部邮件:

错误代码:

定义成员变量
private static Map cachedMap = new HashMap(7000);
private static Boolean firstInvoke = true;
程序是设想在第一次开始对该map变量进行初始化
线程1:
Public Object getMyValue(){
If(firstInvoke){
While(i<7000){
…………
cachedMap.put("new","newValue");
i++;
}
firstInvoke = false;
}
}

线程2:
在线程1对cachedMap对象put的时候,线程2从这个cachedMap中取值
cachedMap.get("new");

错误分析   

       单步Debug是没问题,但代码在多线程情况下工作会出现线程安全。 Hashmap不是读写线程安全的,只有全部只读才是线程安全的,Hashmap在被并发读写使用的时候会出现线程安全问题,一般理解的线程安全问题导致的是数据错误。 而Hashmap多线程同时读写操作时,可能使程序挂起。

以下引用自http://sdh5724.javaeye.com/blog/619130

       分析: 我们知道Hashmap在被并发读写使用的时候, 会抛出ConcurrentModificationException这个异常, 但是JDK文档明确指出, 这个异常抛出是属于 fail-fast 的一个设计方法, 目的是为了开发者能及早的意识到线程安全问题发生。 但是, 这个fail-fast不是一定会发生, 而是可能会发生的行为。 因此, 在一个不确定状态下的下,jvm线程发生持续100%cpu行为是比较容易理解了(for (Entry<K,V> e = table[i]; e != null; e = e.next), 目前只能估计是这个代码进入死循环的状态,还不能非常明确)。

“正确用法”

注意更改HashMap中的内容时是否存在同时并发线程读的情况,如果有, 需要对读写的入口做同步. 如果知道要在多线程情况下读写Map, 建议使用线程安全的ConcurrentHashMap实现代替HashMap。ConcurrentHashMap 可以在不损失线程安全的同时提供很好的并发性。
代码如下:

private static Map cacheMap = new ConcurrentHashMap(7000);
private static Boolean firstInvoke = true;
程序是设想在第一次开始对该map变量进行初始化
线程1:
Public Object getMyValue(){
If(firstInvoke){
While(i<7000){
…………
cachedMap.put("new","newValue");
i++;
}
firstInvoke = false;
}
}

线程2:
在线程1对cachedMap对象put的时候,线程2从这个cachedMap中取值
cachedMap.get("new");

上述解决方案的race condition问题:

这个HashMap不当使用的问题很经典。很多时候我们用“单线程”思维习惯去写代码,不知不觉就忘记了运行时的多线程场景。

其实,我觉得下面的例子中还是有隐含的race condition问题的,那就是在这个if(firstInvoke) then load data and firstInvoke=false这个逻辑中。

即:If(firstInvoke){…  //ß 这里可能会导致多条线程同时进入,导致多次load data

通常我们用一个boolean变量来实现lazy操作, 那么在多线程环境下,要记得使用synchronize关键词 或者 采用volatile类型变量+CAS操作,确保变量被每条线程都能正确的读取和写入。

1. 保险的做法:(在最新JVM中,这种方式是最安全,最可读,性价比最高的,如果JVM支持锁逃逸即Biased Locking,性能也会非常好)

Synchronized(lock){
If(firstInvoke){
Then load data…
firstInvoke = false
}
}

2. 或者,用volatile变量+DCL

Private volatile boolean firstInvoke = true;

If(firstInvoke){

Synchronized(lock){

If(firstInvoke){

Then load data …

firstInvoke = false;

}

}

}

3. SMP友好,但是偷懒的做法,用AtomicBoolean,里面用到了CompareAndSet操作。(volatile只保证变量可见性,Spinning CAS保证操作原子性)

Private AtomicBoolean firstInvoke = new AtomicBoolean(true);

If(firstInvoke.getAndSet(false)){ // cas spinning inside the AtomicBoolean::getAndSet() method

Then load data…

}

4. 最后,最复杂,但是同时满足SMP友好,及性能最佳的:

private AtomicBoolean firstInvoke = new AtomicBoolean(true);

for(;;){

Boolean current = firstInvoke.get();

If(!current){ // the most likely condition branch, see http://pt.alibaba-inc.com/wp/dev_related/optimization_363/likely-unlikely.html

Break;

}

If(firstInvoke.compareAndSet(current,false){

Then load data…

Break;

}

}

在××××代码中,为了确保SMP状态下性能最优,我们在某一些关键地方也用到了上面的CAS+spinning的技巧。

我们也许并不会时时刻刻用到“回字的四种写法”,但是搞清楚JVM内存可见性和操作原子性的基本概念还是必须的,这也是确保写出线程安全代码的前提条件)。

 

参考资料:

http://sdh5724.javaeye.com/blog/619130

http://www.tech-faq.com/race-condition.html

《 The Art of Multiprocessor Programming》 http://book.douban.com/subject/3024605/

race condition by @Shawn

 

相关资料:

Java轻量级锁原理详解(Lightweight Locking)

Java偏向锁实现原理(Biased Locking)

深入理解DCL(双检锁)的安全性

转载于:https://www.cnblogs.com/redcreen/archive/2011/03/29/1999032.html

相关文章:

  • git pull远程所有分支
  • 网卡硬件时间戳之 --- shtx-hardware标志
  • Mina框架与Spring整合配置文件
  • OSGi 4.3引入了Generics与Capabilities
  • Django Web框架
  • DirectoryEntry的使用
  • 使用GZipStream类压缩和解压文件夹
  • mysql sql语句中用括号处理or和and的运算顺序
  • c#中WebBrowser控件使用心得
  • [转载]编写超级可读代码的15个最佳实践
  • 定时器NSTimer
  • 查看系统默认路径
  • 递归法求数组中的最大数
  • codeforces 190A Vasya and the Bus
  • nodejs一周动态(2011-04-25 - 05-01)
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • 30天自制操作系统-2
  • Android系统模拟器绘制实现概述
  • CSS中外联样式表代表的含义
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • go语言学习初探(一)
  • IP路由与转发
  • JavaScript设计模式与开发实践系列之策略模式
  • Linux Process Manage
  • MobX
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 反思总结然后整装待发
  • 缓存与缓冲
  • 如何进阶一名有竞争力的程序员?
  • 使用 Xcode 的 Target 区分开发和生产环境
  • Java性能优化之JVM GC(垃圾回收机制)
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • # .NET Framework中使用命名管道进行进程间通信
  • # 数论-逆元
  • #ubuntu# #git# repository git config --global --add safe.directory
  • (1)SpringCloud 整合Python
  • (七)理解angular中的module和injector,即依赖注入
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (十一)图像的罗伯特梯度锐化
  • (一一四)第九章编程练习
  • (转)c++ std::pair 与 std::make
  • (转载)OpenStack Hacker养成指南
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • /etc/fstab 只读无法修改的解决办法
  • @Async注解的坑,小心
  • @SuppressWarnings(unchecked)代码的作用
  • [ 隧道技术 ] 反弹shell的集中常见方式(二)bash反弹shell
  • [2016.7.Test1] T1 三进制异或
  • [4.9福建四校联考]
  • [bzoj1324]Exca王者之剑_最小割
  • [CVPR2021]Birds of a Feather: Capturing Avian Shape Models from Images