JDK1.7 ConcurrentHashMap 解析

  1. 线程安全之 ConcurrentHashMap
  2. ConcurrentHashMap 的性能优化

力求简单而明确地直指 JDK1.7 ConcurrentHashMap 的设计要点。

线程安全之 ConcurrentHashMap

在 JDK 中,有 java.util.concurrent包,里面存有这一些线程安全的集合类。其中应用最多的就是 ConcurrentHashMap,这是一个线程安全的 HashMap,不同的 JDK 版本可能使用了不同的技巧来保证这个线程安全的特性。这里我讨论的是 JDK1.7 版本的线程安全设计。

JDK7 采用了分段锁的方式来保证 ConcurrentHashMap 的线程安全,分段锁的工作原理是:

HashMap 下需要完成线程安全可以使用 Collections.synchronizedMap 构造一个线程安全的 HashMap,他的原理就是利用 synchronized 关键字对基本上所有的方法进行上锁,是一种非常粗暴的解决方案。可想而知的是低性能换来的线程安全。

ConcurrentHashMap 下有多个 Segment(默认 16 个), Segment 下是一个 HashTable 用来存 key-value。 Segment 是继承 ReentrantLock,可以直接享有相关的锁方法。一个数据存入 ConcurrentHashMap 需要先进行一次 hash 来确定他是在哪个 Segment 下,再对该 Segment 上锁写数据。可以看到这一点已经比前者的实现高明了。

ConcurrentHashMap 的性能优化

上一节中提到了 put 操作会对 Segment 上锁,事实上这里 JDK7 还有所优化,调用 put 方法会先尝试获取锁,tryLock() 这个是 ReentrantLock 的方法,获取不到会继续去尝试 scanAndLockForPut(),这个方法里会不停的 tryLock(),这里会涉及到 MAX_SCAN_RETRIES 次尝试 tryLock(),超过次数了会直接 lock() 获取锁。在操作最后会在 try...finallyunlock() 释放锁。

static final int MAX_SCAN_RETRIES =
            Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;

还有 size() 方法获取 ConcurrentHashMap 的大小时,会有一系列的操作来保证得到准确的 size。

获取 size 大小是一个无限循环,每次循环会先自增一次 retries,只有 retries == RETRIES_BEFORE_LOCK(=2) 时才会将所有的 Segment 都上锁再计算。这里有个 trick,会记录上一次的 Segment 的 modCount 总值,在下一次计算时会比较,如果不相等,那就是再来一次了。这里利用了 map 中 modCount 机制。

for (;;) {
    // 先尝试直接获取 size 大小,这里有计算一个 modCount的数值,会和上次(last)作比较,如果一样的话,说明map没有做增删操作啥的,就是正确的了
    // 如果尝试的次数超过了 RETRIES_BEFORE_LOCK, 就直接去锁 segment 再计数
    if (retries++ == RETRIES_BEFORE_LOCK) {
        for (int j = 0; j < segments.length; ++j)
            ensureSegment(j).lock(); // force creation
    }
    sum = 0L;
    size = 0;
    overflow = false;
    for (int j = 0; j < segments.length; ++j) {
        Segment<K,V> seg = segmentAt(segments, j);
        if (seg != null) {
            sum += seg.modCount;
            int c = seg.count;
            if (c < 0 || (size += c) < 0)
                overflow = true;
        }
    }
    if (sum == last)
        break;
    last = sum;
}

至于其他的方法,涉及到的锁步骤无非也就上面的重复了,就不多谈了。在此留笔,方便日后再记。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nickchenyx@gmail.com

Title:JDK1.7 ConcurrentHashMap 解析

Count:742

Author:nickChen

Created At:2018-04-17, 16:04:46

Updated At:2023-05-08, 23:27:10

Url:http://nickchenyx.github.io/2018/04/17/jdk1-7-concurrenthashmap-summary/

Copyright: 'Attribution-non-commercial-shared in the same way 4.0' Reprint please keep the original link and author.