【源码阅读系列】JDK 8 ConcurrentHashMap 源码分析之 由transfer引发的bug

不阅读源码就不会发现这个事儿

前段时间在阅读ConcurrentHashMap源码,版本JDK 8,目前源码研究已经告一段落。感谢鲁道的ConcurrentHashMap源码分析文章,读到文章,感觉和作者发生了一些交流,解答了很多疑惑,也验证了一些想法。鲁道在简书的addCount分析文章点这里 (文章底部的评论中就有这篇文章发酵的原由)。鲁道还有其他ConcurrentHashMap源码分析的系列文章,在简书、掘金都有分布,感兴趣的同学可以进一步追踪。

推完文章,回到本篇的主题“阅读源码”,期间发生了一件有意思的事情,而且既然是个BUG,就提出来让更多人知道。

ConcurrentHashMap源码分析导读

ConcurrentHashMap的源码据说在 1.8 发生了巨大改变。并发put时,ConcurrentHashMap只会用sync锁住桶节点(我把table[index] 位置的节点称为 桶节点),并发度就是hash数组长度。在并发扩容时,每个线程可以一次转移一个分片区域的桶节点,互不干扰,详见transfer源码 变量 stride ,当然stride最小是16,所以桶不够的时候,是不会有那么多线程都在“并发转移”的。每个线程转移节点时是从后往前,也就是从下标大的节点往下标小的节点方向来处理转移,处理完一段分片后,领取下一段,整个旧table处理进度由ConcurrentHashMap#transferIndex属性控制,它是volatile修饰的,提供更好的可见性。

通过研读transfer相关的源码,知道了在 addCount方法中,第一条进去扩容的节点会把 sizeCtl 设为 rs << RESIZE_STAMP_SHIFT) + 2:(MARK-1)

代码片段:

else if (U.compareAndSwapInt(this, SIZECTL, sc,
                             (rs << RESIZE_STAMP_SHIFT) + 2))

rs的计算方式如下:

int rs = resizeStamp(n);

这个 rs会根据数组长度 n 为 2的多少次幂来进行变化,也就是table长度的一个标识符,取值范围在 32768~32799 之间。32678是 2^15 等于二进制 1000 0000 0000 0000 。

好了,现在我们已经接近问题现场。根据上面的 MARK-1,第一条线程扩容开始后,sizeCtrl已经是一个负数,而在addCount中你会发现在 MARK-1 的代码上面还有这么一段代码:

if (sc < 0) {
    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
        transferIndex <= 0)
        break;
    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
        transfer(tab, nt);
}

sc < 0 是因为发生扩容,sizeCtl已经为负数,那么上面这段代码中  一个负数 sc 如何能与  rs + 1 (rs正数) 和 rs + MAX_RESIZES (结果也是正数)两个都为正数 进行等值判断呢???而且 rs + 1 和 rs + MAX_RESIZES 也不是int溢出附近的值。当时是怎么也想不通负数如何与正数进行比较的,持着怀疑态度我去测试了resizeStamp方法,于是才有前文中我对 rs 取值的验证。

我的博客后面也会更新自己阅读ConcurrentHashMap源码时的一些收获,尽量把过程和结果输出出来。

无巧不成书

有意思的是,在上个月底那段时间阅读源码碰到这个问题时,开始了各种google,在StackOverFlow上正好有一位同学发表了疑似JDK 8 ConcurrentHashMap的BUG,追踪进去后,发现oracle已经采纳了该BUG。BUG链接。而正好就是这位同学,也在鲁道的简书文章下评论了。就是这么巧。相比提出BUG的这位同学,我的动手能力还有待提高......

终于,最后搞明白了真的就是代码写得有问题。该问题还存在于和transfer相关联的方法,只要是调了 transfer的,如addCount、helpTransfer、tryPresize等方法都有一样的BUG。

正确写法本文这里就不贴出了,相信大家思考一下就能得出结论。BUG清单中也有正解供参考。

总结

阅读优秀源码时,敢于质疑,敢于提出猜想,最后用事实去验证自己的猜想。



原文地址:https://www.cnblogs.com/christmad/p/10123390.html

时间: 2024-10-14 08:58:58

【源码阅读系列】JDK 8 ConcurrentHashMap 源码分析之 由transfer引发的bug的相关文章

Spring源码阅读系列总结

最近一段时间,粗略的查看了一下Spring源码,对Spring的两大核心和Spring的组件有了更深入的了解.同时在学习Spring源码时,得了解一些设计模式,不然阅读源码还是有一定难度的,所以一些重要的设计模式简单的做了阐述.同时还会简单的加入一些GOF中提到的设计原则.Spring的源码阅读系列,也暂告一段落.下面是就带你走进Spring世界: Spring系列的引子 1)Spring WebApplicationContext初始化与消亡 这一节帮我们了解Spring是如何初始化WebAp

【Dubbo源码阅读系列】之远程服务调用(上)

今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道远程服务具体的实现,消费者和提供者通过代理类来进行交互!! 一.JAVA 动态代理 简单看一段代码回顾一下动态代理: public class MyInvocationHandler implements InvocationHandler{ private Object object; publi

【Dubbo源码阅读系列】服务暴露之远程暴露

引言 什么叫 远程暴露 ?试着想象着这么一种场景:假设我们新增了一台服务器 A,专门用于发送短信提示给指定用户.那么问题来了,我们的 Message 服务上线之后,应该如何告知调用方服务器,服务器 A 提供了 Message 功能?那么我们是不是可以把目前已提供的服务暴露在一个地方,让调用方知道某台机器提供了某个特定功能?带着这样的假设,我们今天就来聊聊 Dubbo 服务暴露之远程暴露!! 服务远程暴露 先回顾一下上篇文章,上篇文章我们聊到了 ServiceConfig 的 export() 方

源码阅读系列:源码阅读方法

一.前提条件 1.纯熟扎实的语言基础 ??如果你学java,却对反射.泛型.注解一直半解,还是不要去读什么框架了,回去把java基础打扎实反而对你自身更有益. 2.UML能力 ??在软件工程中,UML在软件的不同生命周期阶段扮演着非常重要的角色,没有好的UML水平,面对大型的项目源码会束手无策. 3.对业务的理解 ??如果你要阅读的项目业务性比较强,事先对业务有一定的了解是必须的. 4.设计模式.重构的掌握 ??编程语言什么的没什么好说.着重提一个:设计模式由于Android源代码用到各种各样的

深入理解JAVA集合系列二:ConcurrentHashMap源码解读

HashMap和Hashtable的区别 在正式开始这篇文章的主题之前,我们先来比较下HashMap和Hashtable之间的差异点: 1.Hashtable是线程安全的,它对外提供的所有方法都是都使用了synchronized,是同步的,而HashMap是非线程安全的. 2.Hashtable不允许value为空,否则会抛出空指针异常: 而HashMap中key.value都可以为空. 1 public synchronized V put(K key, V value) { 2 // Mak

JDK源码学习系列07----Stack

                                                               JDK源码学习系列07----Stack 1.Stack源码非常简单 package java.util; public class Stack<E> extends Vector<E> { // 版本ID.这个用于版本升级控制,这里不须理会! private static final long serialVersionUID = 122446316454

【原】AFNetworking源码阅读(六)

[原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AFNetworking的网络安全策略,尤其指HTTPS(大家可以先简单了解下HTTPS).再一个就是分析下AFNetworkReachabilityManager文件,看看AFNetworking如何解决网络状态的检测. 2. AFSecurityPolicy - 网络安全策略 之前我们在AFURLS

CI框架源码阅读笔记8 控制器Controller.php

最近时间有些紧,源码阅读系列更新有些慢.鉴于Controller中代码比较少,本次Blog先更新该文件的源码分析. 在经过路由分发之后,实际的应用Controller接管用户的所有请求,并负责与用户数据的交互.CI中所有的应用控制器都应该是CI_Controller的子类(除非你扩展了CI的核心,那么你的Controller父类可以是MY_Controller). 在应用程序控制器中,我们经常会用到这样的代码: /* 加载配置文件 */ $this->load->config("co

Spring源码由浅入深系列五 CreateBean

blog宗旨:用图说话. 附:目录 Spring源码由浅入深系列五 GetBean Spring源码由浅入深系列四 创建BeanFactory Spring源码由浅入深系列三 refresh Spring源码由浅入深系列二 类结构 Spring源码由浅入深系列一 简介