ConcurrentHashMap是Java一个非常重要的并发集合类,它提供了线程安全哈希实现。其初衷是为了优化同步HashMap,减少线程竞争提高并发访问效率。随着Java发展,ConcurrentHashMap在1.7和1.8中经历了显著的变化。以下内容将深入探索两个版本区别,同时结合源码底层实现来进行说明

1. Java 1.7中的ConcurrentHashMap

在Java 1.7(及之前的版本)中,ConcurrentHashMap采用分段锁(Segmentation)的概念,其核心是将数据分成一段一段地存储然后为每一段数据配备一把锁。

1.1 核心实现

在Java 1.7中,ConcurrentHashMap内部维护一个Segment数组每个Segment继承自ReentrantLock并且它内部本质上是一个Hash。这样做的好处是能够减小锁的粒度,提高并发访问效率默认Segment 数量为 16可以通过构造函数修改默认值。当需要putget一个元素时,线程首先通过hash定位到具体的Segment然后对应的Segment上进行锁定操作

static final class Segment<K,V&gt; extends ReentrantLock implements Serializable {
    // 省略其他属性方法

    // Segment内部的HashEntry数组
    transient volatile HashEntry<K,V&gt;[] table;

    Segment(float loadFactor, int threshold, HashEntry<K,V>[] tab) {
        this.loadFactor = loadFactor;
        this.threshold = threshold;
        this.table = tab;
    }

    // 其他方法...
}

1.2 写操作

当进行写操作时,需要首先定位到具体的Segment,然后对其加锁执行操作后再解锁。这意味着同时只有一个线程可以一个Segment内进行写操作,但是多个线程可以并发对不同的Segment进行操作。

public V put(K key, V value) {
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    int i = (hash >>> segmentShift) &amp; segmentMask;
    return segments[i].put(key, hash, value, false);
}

1.3 读操作

对于读操作,如果没有进行结构修改可以允许一定程度的并发。如果读操作需要确保最新数据读取可能需要对Segment进行加锁

2. Java 1.8中的ConcurrentHashMap

Java 8中对ConcurrentHashMap的实现进行了重大的改进。在这个版本中,去掉了Segment概念,转而使用CAS操作(Compare-And-Swap)和synchronized关键字配合节点的锁实现高效的并发控制

2.1 核心实现

在Java 1.8中,ConcurrentHashMap内部主要是由Node数组构成,每个Node包含一个keyvalue键值对。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V value;
    volatile Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    // 其他方法...
}

2.2 写操作

写入时,如果目标位置为空(指的是 hash值 或数组上面的链表),将使用CAS操作进行写入。如果已经有节点存在,那么就使用synchronized锁定节点,然后在链表上进行操作,如果是红黑树则在树上进行操作。

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        // ...
        if ((f = tabAt(tab, i = (n - 1) &amp; hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        // ...
    }
    return null;
}

2.3 读操作

Java 8的ConcurrentHashMap在读操作上基本加锁(除非在读操作过程检测到写操作正在进行),利用volatile关键字读写内存语义来保证可见性,从而大大提高读操作的并发性。

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    if ((tab = table) != null &amp;&amp; (n = tab.length) > 0 &amp;&amp;
        (e = tabAt(tab, (n - 1) &amp; h)) != null) {
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null &amp;&amp; key.equals(ek)))
                return e.val;
        }
        // ...
    }
    return null;
}

3. 结构优化

自Java 1.8开始,ConcurrentHashMap内部结构由链表逐渐转化为红黑树,以减少搜索时间链表在元素数量增加到一定程度时会转换红黑树结构。

4. 总结

Java 1.7的ConcurrentHashMap通过分段锁实现高并发,但它的并发度受限于Segment的数量。而Java 1.8通过精细化控制,只在必需时进行锁定,显著提升读写性能,尤其是读操作几乎不受影响,这对于读多写少的场景来说是一个巨大的优化。

原文地址:https://blog.csdn.net/weixin_44183847/article/details/134820527

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_47790.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注