传统的hashmap如今由于线程安全问题,已经减少使用,所以今天我们就来介绍一下新成员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常见问答相关知识,请一直关注我们的网站了解详情吧。