千家信息网

如何解析jdk8中的ConcurrentHashMap源码

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章给大家介绍如何解析jdk8中的ConcurrentHashMap源码,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。java.util.concurrent 这个包下面的
千家信息网最后更新 2026年11月02日如何解析jdk8中的ConcurrentHashMap源码

这篇文章给大家介绍如何解析jdk8中的ConcurrentHashMap源码,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

  1. java.util.concurrent 这个包下面的 类都很经典。

  2. ConcurrentHashMap 这个类是java中讨论最多的,也是争论最多的类了。很多人对这个类很好奇。

  3. 作为并发集合,大家比较关心 读写,锁,与 map的散列。

  4. 读写如何的锁

  5. get操作

    Java代码 下载

    明显是没有上锁的。包括所有的读操作。都是不上锁的。

    1. public V get(Object key) {

    2. Node[] tab; Node e, p; int n, eh; K ek;

    3. int h = spread(key.hashCode());

    4. if ((tab = table) != null && (n = tab.length) > 0 &&

    5. (e = tabAt(tab, (n - 1) & h)) != null) {

    6. if ((eh = e.hash) == h) {

    7. if ((ek = e.key) == key || (ek != null && key.equals(ek)))

    8. return e.val;

    9. }

    10. else if (eh < 0)

    11. // TreeBin 操作

    12. return (p = e.find(h, key)) != null ? p.val : null;

    13. while ((e = e.next) != null) {

    14. if (e.hash == h &&

    15. ((ek = e.key) == key || (ek != null && key.equals(ek))))

    16. return e.val;

    17. }

    18. }

    19. return null;

    20. }

  6. put操作(remove等修改操作)

  7. 所有读操作是这样一个模式,如果hash桶中坐标没有数据。就使用CAS 操作。如果有数据。就使用synchronized关键字。比起jdk1.7,1.6使用读写锁,代码比较简洁,同样使用cas操作比 读写锁的性呢过开销底得太多了。但是程序设计变得十分复杂,请下添加的代码 下载

  8. Java代码

    1. final V putVal(K key, V value, boolean onlyIfAbsent) {

    2. if (key == null || value == null) throw new NullPointerException();

    3. int hash = spread(key.hashCode());

    4. int binCount = 0;

    5. //为什么要这个循序,大家会很疑惑

    6. //其实很简单,因为多线程操作,然后没有使用锁,使用 unsafe,多个unsafe不是原子性的,在多线程的情况下,会出现问题。所以使用for来解决这个问题

    7. for (Node[] tab = table;;) {

    8. // f 是节点,n是数组长度,i是hash与数组长度的数组下标,fh是在数组下标已经坐在的节点的hash值

    9. Node f; int n, i, fh;

    10. if (tab == null || (n = tab.length) == 0)

    11. tab = initTable();//延迟初始化 table

    12. //通过unsafe 判断 下标是否有 节点

    13. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

    14. //如果不存在。就创建一个节点 通过 unsafe加入到数组中,所以读是不加锁的

    15. //注意:如果在多个线程同时加入 hash后同一下标的node,那么只有一个会成功,其他失败。失败的就会在再次循序。

    16. //这是循环解决的问题之一

    17. if (casTabAt(tab, i, null,

    18. new Node(hash, key, value, null)))

    19. break; // no lock when adding to empty bin

    20. }

    21. //大家很奇怪这个 if是干什么用额,去了解ForwardingNode这个对象

    22. //这个对象在 hash散列的时候用,原来的一个节点会重新散列到 下个表,原来表的节点的hash就成为了 moved

    23. else if ((fh = f.hash) == MOVED)

    24. tab = helpTransfer(tab, f);

    25. else {

    26. V oldVal = null;

    27. //如果节点存在,就需要添加链了。

    28. //添加链的时候 就上锁 这个节点,那么所有在这个节点的更新操作都会上锁

    29. //这里上锁,比双桶的开销小多了。如果设计好,可以说几乎忽略不计。

    30. synchronized (f) {

    31. if (tabAt(tab, i) == f) {

    32. if (fh >= 0) {

    33. //添加链表

    34. binCount = 1;

    35. for (Node e = f;; ++binCount) {

    36. K ek;

    37. if (e.hash == hash &&

    38. ((ek = e.key) == key ||

    39. (ek != null && key.equals(ek)))) {

    40. oldVal = e.val;

    41. if (!onlyIfAbsent)

    42. e.val = value;

    43. break;

    44. }

    45. Node pred = e;

    46. if ((e = e.next) == null) {

    47. pred.next = new Node(hash, key,

    48. value, null);

    49. break;

    50. }

    51. }

    52. }

    53. //添加 在tree下添加节点

    54. else if (f instanceof TreeBin) {

    55. Node p;

    56. binCount = 2;

    57. if ((p = ((TreeBin)f).putTreeVal(hash, key,

    58. value)) != null) {

    59. oldVal = p.val;

    60. if (!onlyIfAbsent)

    61. p.val = value;

    62. }

    63. }

    64. }

    65. }

    66. //java8,明确的改革

    67. if (binCount != 0) {

    68. //binCount是链表的操作次数,操作多少次。表示链表有多长。当链表大于等于8的时候,链表会变成 tree

    69. if (binCount >= TREEIFY_THRESHOLD)

    70. treeifyBin(tab, i);

    71. if (oldVal != null)

    72. return oldVal;

    73. break;

    74. }

    75. }

    76. }

    77. addCount(1L, binCount);

    78. return null;

    79. }

  9. 这里是大家最关心的要点,重新散列,也就是重新hash

    Java代码 下载

    1. final V putVal(K key, V value, boolean onlyIfAbsent) {

    2. if (key == null || value == null) throw new NullPointerException();

    3. int hash = spread(key.hashCode());

    4. int binCount = 0;

    5. //为什么要这个循序,大家会很疑惑

    6. //其实很简单,因为多线程操作,然后没有使用锁,使用 unsafe,多个unsafe不是原子性的,在多线程的情况下,会出现问题。所以使用for来解决这个问题

    7. for (Node[] tab = table;;) {

    8. // f 是节点,n是数组长度,i是hash与数组长度的数组下标,fh是在数组下标已经坐在的节点的hash值

    9. Node f; int n, i, fh;

    10. if (tab == null || (n = tab.length) == 0)

    11. tab = initTable();//延迟初始化 table

    12. //通过unsafe 判断 下标是否有 节点

    13. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

    14. //如果不存在。就创建一个节点 通过 unsafe加入到数组中,所以读是不加锁的

    15. //注意:如果在多个线程同时加入 hash后同一下标的node,那么只有一个会成功,其他失败。失败的就会在再次循序。

    16. //这是循环解决的问题之一

    17. if (casTabAt(tab, i, null,

    18. new Node(hash, key, value, null)))

    19. break; // no lock when adding to empty bin

    20. }

    21. //大家很奇怪这个 if是干什么用额,去了解ForwardingNode这个对象

    22. //这个对象在 hash散列的时候用,原来的一个节点会重新散列到 下个表,原来表的节点的hash就成为了 moved

    23. else if ((fh = f.hash) == MOVED)

    24. tab = helpTransfer(tab, f);

    25. else {

    26. V oldVal = null;

    27. //如果节点存在,就需要添加链了。

    28. //添加链的时候 就上锁 这个节点,那么所有在这个节点的更新操作都会上锁

    29. //这里上锁,比双桶的开销小多了。如果设计好,可以说几乎忽略不计。

    30. synchronized (f) {

    31. if (tabAt(tab, i) == f) {

    32. if (fh >= 0) {

    33. //添加链表

    34. binCount = 1;

    35. for (Node e = f;; ++binCount) {

    36. K ek;

    37. if (e.hash == hash &&

    38. ((ek = e.key) == key ||

    39. (ek != null && key.equals(ek)))) {

    40. oldVal = e.val;

    41. if (!onlyIfAbsent)

    42. e.val = value;

    43. break;

    44. }

    45. Node pred = e;

    46. if ((e = e.next) == null) {

    47. pred.next = new Node(hash, key,

    48. value, null);

    49. break;

    50. }

    51. }

    52. }

    53. //添加 在tree下添加节点

    54. else if (f instanceof TreeBin) {

    55. Node p;

    56. binCount = 2;

    57. if ((p = ((TreeBin)f).putTreeVal(hash, key,

    58. value)) != null) {

    59. oldVal = p.val;

    60. if (!onlyIfAbsent)

    61. p.val = value;

    62. }

    63. }

    64. }

    65. }

    66. //java8,明确的改革

    67. if (binCount != 0) {

    68. //binCount是链表的操作次数,操作多少次。表示链表有多长。当链表大于等于8的时候,链表会变成 tree

    69. if (binCount >= TREEIFY_THRESHOLD)

    70. treeifyBin(tab, i);

    71. if (oldVal != null)

    72. return oldVal;

    73. break;

    74. }

    75. }

    76. }

    77. addCount(1L, binCount);

    78. return null;

    79. }

    80. private final void addCount(long x, int check) {

    81. CounterCell[] as; long b, s;

    82. //这个判断的目的是 解决 unsafu的死循环问题

    83. if ((as = counterCells) != null ||

    84. !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {

    85. CounterCell a; long v; int m;

    86. boolean uncontended = true;

    87. if (as == null || (m = as.length - 1) < 0 ||

    88. (a = as[ThreadLocalRandom.getProbe() & m]) == null ||

    89. !(uncontended =

    90. U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {

    91. fullAddCount(x, uncontended);

    92. return;

    93. }

    94. if (check <= 1)

    95. return;

    96. s = sumCount();

    97. }

    98. // 有一个问题 ,只有添加操作是 大于0的,那么 没有hash收缩功能

    99. if (check >= 0) {

    100. Node[] tab, nt; int n, sc;

    101. //我靠,这里又有一个循序,是解决什么问题了?

    102. //sc也就是 sizeCtl 等于 -1的情况只有,table初始化与序列化的时候。

    103. //这个循环是保证 序列化之后,还可以加入数据,目测是这样,不敢保证。

    104. while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&

    105. (n = tab.length) < MAXIMUM_CAPACITY) {

    106. int rs = resizeStamp(n);

    107. // 这个if 基本可以忽略

    108. if (sc < 0) {

    109. if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||

    110. sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||

    111. transferIndex <= 0)

    112. break;

    113. if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))

    114. transfer(tab, nt);

    115. }

    116. //把扩容的值 替换原先的值

    117. //并发情况下,多个线程都到达这步,只有一个操作会成功,成功之后其他的线程都会进不来这个。

    118. else if (U.compareAndSwapInt(this, SIZECTL, sc,

    119. (rs << RESIZE_STAMP_SHIFT) + 2))

    120. transfer(tab, null);

    121. s = sumCount();

    122. }

    123. }

    124. }

    125. private final void transfer(Node[] tab, Node[] nextTab) {

    126. int n = tab.length, stride;

    127. if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)

    128. stride = MIN_TRANSFER_STRIDE; // subdivide range

    129. if (nextTab == null) { // initiating

    130. try {

    131. @SuppressWarnings("unchecked")

    132. Node[] nt = (Node[])new Node[n << 1];

    133. nextTab = nt;

    134. } catch (Throwable ex) { // try to cope with OOME

    135. sizeCtl = Integer.MAX_VALUE;

    136. return;

    137. }

    138. nextTable = nextTab;

    139. transferIndex = n;

    140. }

    141. int nextn = nextTab.length;

    142. // good 这个对象,的设计。让散列操不会堵塞

    143. ForwardingNode fwd = new ForwardingNode(nextTab);

    144. boolean advance = true; // 只要过了 第一个 while 基本所有操作都会 advance = true;,每次循环都要进入 while 那么 i 这个值才会改变

    145. boolean finishing = false; // to ensure sweep before committing nextTab

    146. for (int i = 0, bound = 0;;) {

    147. Node f; int fh;

    148. //这个 while 真心不怎么明白

    149. //唯一能解释的是,防止散列完成,才知道多线程操作问题,快速知道多线程问题

    150. while (advance) {

    151. int nextIndex, nextBound;

    152. if (--i >= bound || finishing)

    153. advance = false;

    154. //这个判断只可能进来一次,

    155. else if ((nextIndex = transferIndex) <= 0) {

    156. i = -1;

    157. advance = false;

    158. }

    159. //比如 n 等于 128,stride 为 128 >>>3 /8 =2,

    160. //i = 128,bound -126,那么每两次就会在进来一次,那么就会知道,(nextIndex = transferIndex) 这操作就会知道多线程在操作

    161. else if (U.compareAndSwapInt

    162. (this, TRANSFERINDEX, nextIndex,

    163. nextBound = (nextIndex > stride ?

    164. nextIndex - stride : 0))) {

    165. bound = nextBound;

    166. i = nextIndex - 1;

    167. advance = false;

    168. }

    169. }

    170. //

    171. // 那个操作会让 i 大于等于 n ,这个真想不到

    172. // i + n 也大于等于不了 nextn 啊,

    173. //只有一个可能,在强并发下,有两个线程都进入了。进行操作了。没错

    174. //

    175. if (i < 0 || i >= n || i + n >= nextn) {

    176. int sc;

    177. if (finishing) {

    178. nextTable = null;

    179. table = nextTab;

    180. sizeCtl = (n << 1) - (n >>> 1);

    181. return;

    182. }

    183. // 先是 sc -1 这个操作没有错。应该addcont里面的判断是 是需要减一的,没有在addcount减,放到这里,可以减少操作

    184. if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {

    185. //这个操作会排除 最后进来之前的线程操作。

    186. //怎么做到额,请看addCount方法的 2286行

    187. if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)

    188. return;

    189. finishing = advance = true;

    190. i = n; // recheck before commit

    191. }

    192. }

    193. else if ((f = tabAt(tab, i)) == null)

    194. advance = casTabAt(tab, i, null, fwd);

    195. else if ((fh = f.hash) == MOVED)

    196. advance = true; // already processed

    197. else {

    198. //只有对节点操作才会上锁,性能非常

    199. //散列这里与其他版本的不同,散列的思路很好,因为每次扩展都是2倍,那么扩张之后的hash,在进行一次移位,等于1的就在原有的地方加上扩展之前的系数(比如 从16扩展到32,那么hash,下标1的桶,这个桶会分裂成2个桶,一个还在1下表,一个在16+1下标。

    200. synchronized (f) {

    201. if (tabAt(tab, i) == f) {

    202. Node ln, hn;

    203. if (fh >= 0) {

    204. int runBit = fh & n;

    205. Node lastRun = f;

    206. //寻分裂出来的 lastRun

    207. for (Node p = f.next; p != null; p = p.next) {

    208. int b = p.hash & n;

    209. if (b != runBit) {

    210. runBit = b;

    211. lastRun = p;

    212. }

    213. }

    214. if (runBit == 0) {

    215. ln = lastRun;

    216. hn = null;

    217. }

    218. else {

    219. hn = lastRun;

    220. ln = null;

    221. }

    222. //把一个链表,分裂成两个列表

    223. for (Node p = f; p != lastRun; p = p.next) {

    224. int ph = p.hash; K pk = p.key; V pv = p.val;

    225. if ((ph & n) == 0)

    226. ln = new Node(ph, pk, pv, ln);

    227. else

    228. hn = new Node(ph, pk, pv, hn);

    229. }

    230. //把两个链表,加入到下个tab

    231. setTabAt(nextTab, i, ln);

    232. setTabAt(nextTab, i + n, hn);

    233. //把分裂的 桶,替换成 fwd

    234. setTabAt(tab, i, fwd);

    235. advance = true;

    236. }

    237. else if (f instanceof TreeBin) {

    238. TreeBin t = (TreeBin)f;

    239. TreeNode lo = null, loTail = null;

    240. TreeNode hi = null, hiTail = null;

    241. int lc = 0, hc = 0;

    242. for (Node e = t.first; e != null; e = e.next) {

    243. int h = e.hash;

    244. TreeNode p = new TreeNode

    245. (h, e.key, e.val, null, null);

    246. if ((h & n) == 0) {

    247. if ((p.prev = loTail) == null)

    248. lo = p;

    249. else

    250. loTail.next = p;

    251. loTail = p;

    252. ++lc;

    253. }

    254. else {

    255. if ((p.prev = hiTail) == null)

    256. hi = p;

    257. else

    258. hiTail.next = p;

    259. hiTail = p;

    260. ++hc;

    261. }

    262. }

    263. ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :

    264. (hc != 0) ? new TreeBin(lo) : t;

    265. hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :

    266. (lc != 0) ? new TreeBin(hi) : t;

    267. setTabAt(nextTab, i, ln);

    268. setTabAt(nextTab, i + n, hn);

    269. setTabAt(tab, i, fwd);

    270. advance = true;

    271. }

    272. }

    273. }

    274. }

    275. }

    276. }

  10. 看过 jdk8文档的伙计都知道,jdk8,不再是双桶。而进入了 二叉树结构。请看上面的添加操作,与散列操作就知道。TreeBin对象与TreeNode对象就清除了。至于性能怎么样,可以用jdk8文档的标准,性能刚刚的。

  11. 使用ForwardingNode对象,保证在散列的时候读写操作是在nextbat里面。非常优秀的设计。

  12. 使用Unsafe 对象在性能上带来疯狂的性能提升,但是也给程序设计带来了,超大的复杂性。

关于如何解析jdk8中的ConcurrentHashMap源码就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

节点 线程 问题 下标 数组 对象 时候 只有 设计 代码 多个 性能 循环 成功 情况 长度 两个 开销 数据 是在 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网高科技 中关村互联网 科技集团 互联网科技保险 上海互联网科技有限公司 合肥互联网科技公司 互联网与新科技 亿账通互联网科技 成都狮子座互联网科技 甘肃拓世互联网科技有限公司 中国最先进互联网科技公司 互联网汽车科技小镇 互联网科技有哪些公司 开一家科技互联网公司需要多少钱 青海科技产业互联网发展 健康食品互联网创新科技园项目 燊美集互联网科技有限公司 科技赋能视角下互联网保险发展 艺电能源互联网科技有限公司招聘 长沙易易互联网科技有限公司 河南商务圈互联网科技有限公司 科技互联网龙头 甘肃拓世互联网科技有限公司 蓝色互联网科技 新华互联网科技2019 小米互联网科技园设置 智腾互联网科技有限公司 杭州壁岐互联网科技有限公司 米聚互联网科技南京有限公司排面 长沙渺峰互联网科技有限公司 互联网科技博主张洲
0