读书笔记《大型网站系统与Java中间件实践》第一章1.2.2.3 同步陷阱

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读书笔记《大型网站系统与Java中间件实践》第一章1.2.2.3 同步陷阱相关的知识,希望对你有一定的参考价值。

这一小节给了一段代码,

 1 public class TestClass {
 2     private HashMap<String, Integer> map = new HashMap<String, Integer>();
 3     public synchronized void add(String key){
 4           Integer value = map.get(key);
 5           if(value == null){
 6               map.put(key,1);
 7           }else{
 8              map.put(key, value + 1);
 9           }
10     }  
11 }                

可以看出,这是对一个不保证线程安全的容器做写入同步。书中该代码前的语境是这样:

。。。不过,需要在这里提一点的是,有时通过加锁把使用线程不安全容器的代码改为使用线程安全容器的代码时,会遇到笔者之前遇到过的一个陷阱,即在一个使用 map 存储信息后统计总数的例子中,map 中的 value 整型使用线程不安全的 HashMap 代码是这样写的。。。

由此可见,一开始贴的代码是正确也推荐使用的。但之后作者在这贴了一段代码并叙述

。。。如果我们使用 ConcurrentHashMap 来替换 HashMap,并且仅仅是去掉 synchronized 关键字,那么就出问题了。问题不复杂,大家可以自己来思考答案。

代码如下

 1 public class TestClass {
 2     private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
 3     public void add(String key){
 4           Integer value = map.get(key);
 5           if(value == null){
 6               map.put(key,1);
 7           }else{
 8              map.put(key, value + 1);
 9           }
10     }  
11 }       

这里的 ConcurrentHashMap 是 Java 1.5 版本出的线程安全容器,采用分段锁(lock striking)做细粒度的同步,针对对并发的访问,动作的吞吐量也不至于大打折扣。

乍一看好像没有什么不对劲的,既然选择一个线程安全容器,那么监视器可以移除也不会有什么问题吧?

也不会有什么问题吧?(确认+1)

也不会有什么问题吧?(确认 again)

 

 

如果不是作者提到这是个陷阱,笔者作为个小菜鸟将来有120%的可能会掉这个坑里。。。那就来分析下这段代码究竟是有什么问题吧!

 

可以看出的是,这个 add 方法是个 check then act 的动作,而这就会造成常见的 race condition 。来看一段 stackoverflow 上的解释

A race condition occurs when two or more threads can access shared data and they try to change it at the same time. Because the thread scheduling algorithm can swap between threads at any time, you don‘t know the order in which the threads will attempt to access the shared data. Therefore, the result of the change in data is dependent on the thread scheduling algorithm, i.e. both threads are "racing" to access/change the data.

Problems often occur when one thread does a "check-then-act" (e.g. "check" if the value is X, then "act" to do something that depends on the value being X) and another thread does something to the value in between the "check" and the "act". E.g:

接下来也给出简洁的代码阐述这一现象:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

上面说,并发的访问或者更新操作,如果不加锁控制同步,那 if 内的代码的先决条件并不一定就是显式 check 的,也许其他线程已经对那个数据动了手脚。到这就很明显了,有点并发意识的朋友大概都能理解了吧。嘻嘻。

 

由这个代码陷阱让笔者想起了一些概念,一方面是本文所说的访问操作的同步,另一方面呢,就是这个 map 的发布,也就是常说的暴露引用什么的,要留心即使这里锁了这个方法,当别处的代码也可以访问更新这个 map 时,依旧会导致同样的陷阱。当然,最好是直接
synchronized(map){...},直接对 map 加锁,避免使用代码的内置锁。


以上是关于读书笔记《大型网站系统与Java中间件实践》第一章1.2.2.3 同步陷阱的主要内容,如果未能解决你的问题,请参考以下文章

《大型网站系统与JAVA中间件实践学习笔记》-1

大型网站架构演变史(含技术栈与价值观)

大型网站系统与JAVA中间件实践pdf

《深入理解Java虚拟机:JVM高级特性与最佳实践》读书笔记

大型网站架构学习笔记

大型网站架构学习笔记