Kevin's Zone

  • Home
  • Project
  • ContactMe
  • Login
  • Register
Kevin
受苦即了苦, 享福即消福, 福尽而死
  1. 首页
  2. 学习笔记
  3. Java
  4. 正文

ConcurrentHashMap 实现原理

2018年4月14日 2856点热度 0人点赞 0条评论

由于 HashMap 是一个线程不安全的容器,主要体现在容量大于总量*负载因子发生扩容时会出现环形链表从而导致死循环。

因此需要支持线程安全的并发容器 ConcurrentHashMap 。

[title]数据结构[/title]

如图所示,是由 Segment 数组、HashEntry 数组组成,和 HashMap 一样,仍然是数组加链表组成。

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

[title]get 方法[/title]

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

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

[title]put 方法[/title]

内部 HashEntry 类 :

    static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;

        HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
            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 发生了变化就认为容器的大小也在发生变化。

 

标签: 暂无
最后更新:2018年4月14日

Kevin

这个人很懒,什么都没留下

点赞
< 上一篇

文章评论

您需要 登录 之后才可以评论
文章目录
  • [title]数据结构[/title]
  • [title]get 方法[/title]
  • [title]put 方法[/title]
  • size 方法

COPYRIGHT © 2021 Kevin's Zone. ALL RIGHTS RESERVED.

京ICP备16064400号-1