Concurrenthashmap锁的实现原理有哪些?它又有什么实现方法?

TheDisguiser 2020-05-06 17:03:42 java常见问答 10285

传统的hashmap如今由于线程安全问题,已经减少使用,所以今天我们就来介绍一下新成员ConcurrentHashMap,它是支持线程安全的,一起来了解一下吧。

首先让我们来看一张ConcurrentHashMap的数据结构图来了解一下基本数据结构吧。

Concurrenthashmap锁的实现

如上图所示,ConcurrentHashMap是由 Segment 数组、HashEntry 两种数组组成,和 HashMap 一样,它也是数组加链表组成。

ConcurrentHashMap 是采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会如 HashTable 一样,不管是 put 还是 get 操作都需要做同步处理,理论上来说, ConcurrentHashMap 是支持 CurrencyLevel 线程并发的。每当一个线程占用锁时再访问另一个 Segment,不会影响到其他的 Segment。

get 方法

ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁。

只要把 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值

put 方法

HashEntry 类 :

static final class HashEntry
{
    final int hash;
    final K key;
    volatile V
    value;
    volatile HashEntrynext;
    HashEntry(int hash, K key, V value, HashEntrynext)
    {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
}

虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。

首先也是通过 Key 的 Hash 定位到具体的 Segment,在 put 之前会进行一次扩容校验。在这比 HashMap 要好的一点是:HashMap 是插入元素之后再看是否需要扩容,有可能扩容之后后续就没有插入就浪费了本次扩容。注:扩容是非常消耗性能的

ConcurrentHashMap却不一样,它是先将数据插入之后再检查是否需要扩容,之后再做插入。

size 方法

每个 Segment 都有一个 volatile 修饰的全局变量 count ,求整个 ConcurrentHashMap 的 size 时很明显就是将所有的 count 累加即可。但是 volatile 修饰的变量却不能保证多线程的原子性,所有直接累加很容易出现并发问题。

但如果每次调用 size 方法将其余的修改操作加锁效率也很低。所以做法是先尝试两次将 count 累加,如果容器的 count发生了变化再加锁来统计 size。

那ConcurrentHashMap 是怎么知道在统计时大小会发生变化呢,这是因为 每个Segment 都有一个 modCount 变量,每当进行一次 put remove 等操作,modCount 将会 +1。只要 modCount 发生了变化就认为容器的大小也在发生变化。

这就是本篇文章的全部介绍了,更多java常见问答相关知识,请一直关注我们的网站了解详情吧。