从volatile解读ConcurrentHashMap(jdk1.6.0)无锁读

作者:绫萱

volatile常常用于修饰多线程共享变量,用来保证该变量的可见性。volatile的语意:某个写线程对volatile变量的写入马上可以被后续的某个读线程“看”到。

volatile保证可见性的原理:volatile是通过在编译器生成字节码时,在对volatile变量进行读写指令序列的前后加入内存屏障,来禁止一些处理器重排序保证写入一定发生在读之前的这种happen-before关系。

 

简单理解:在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个线程本地内存中;以后再取变量值时,就直接从本地内存中取值;当变量值在本线程里改变时,会同时把变量的新值copy到本地内存中,以便保持一致;在某个特定的时候,将本地内存的更改写到系统主内存中去;当变量在因别的线程等而改变了值,并且该变化没有写到系统主内存,本次线程的本地内存中的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致;但是当变量被volatile修饰后,每次更改该变量的时候会将更改结果写到系统主内存中,利用多处理器的缓存一致性,其他处理器会发现自己的缓存行对应的内存地址被修改,就会将自己处理器的缓存行设置为失效,并强制从系统主内存获取最新的数据。这样就能保证即使在别的线程中改变了该变量的值,在本线程中也能取到最新更改后的值。 ConcurrentHashMap之所以有较好的并发性是因为ConcurrentHashMap是无锁读和加锁写,并且利用了分段锁(不是在所有的entry上加锁,而是在一部分entry上加锁)。

那ConcurrentHashMap是怎么实现无锁读的呢?

这是在jdk1.6.0中的读的实现。


    不得不赞叹,作者深厚的功底啊,当执行读的时候,先判断count,count就是一个Segment(充当锁的角色)所守护HashEntry的数量。

 
    这里的count是被volatile修饰的。当对这段表的结构进行更改时,在退出前都会去更改count。由于volatile的语意:某个写线程对volatile变量的写入马上可以被后续的某个读线程“看”到,所以这里对count的读一定发生在对count写之后,获得是最新的count。在无锁读的方法中,首先去读取这个最近的count,保证了在执行无锁读的时候表的结构没有被改变。(利用了volatile变量写读的happen-before关系)。

   同时当把value设置为volatile时,其他线程所做的改变就能马上被当前线程感知。这样就能支持多个线程并发读了~

   不过我们也知道volatile并不能保证线程安全,它是轻量级的synchronized。

   要使 volatile变量提供理想的线程安全,必须同时满足下面两个条件:
              ● 对变量的写操作不依赖于当前值。
              ● 该变量没有包含在具有其他变量的不变式中。
举例:线程安全计数器的自增操作,其实是由3个操作读取-修改-写入操作序列组成的组合操作,volatile不能保证原子性,不能保证在操作期间该变量的值不会改变。
   其实这是一种常见的volatile的利用场景——开销较低的读-写锁策略。如果读操作远远超过写操作,您可以结合使用内部锁和 volatile变量来减少公共代码路径的开销。这样读操作只是volatile读操作,性能优于一个无竞争的锁获取的开销。但是当需要对该变量执行写操作,应该加锁。

  PS:这里ConcurrentHashMap也有加锁读的情况。利用方法  V readValueUnderLock(HashEntry<K,V> e)。只有value为空的时候,才会加锁读,这种情况就是编译器对value的赋值操作进行重排序了。

    感谢家纯师兄的订正和指导。
 

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 从volatile解读ConcurrentHashMap(jdk1.6.0)无锁读

FavoriteLoading添加本文到我的收藏
  • Trackback 关闭
  • 评论 (0)
  1. 暂无评论

您必须 登陆 后才能发表评论

return top