ConcurrentHashMap.jdk1.8 put操作:

本文详细解析了ConcurrentHashMap的put操作实现原理,包括数组初始化、节点添加、扩容协助及树化过程。深入探讨了其在多线程环境下的高效性和并发控制策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 put操作:

public V put(K key, V value) {
        return putVal(key, value, false);
    }

    /** Implementation for put and putIfAbsent */
    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;
            if (tab == null || (n = tab.length) == 0)
                // 1.初始化数组
                tab = initTable();
            // 2.取出key的hash值对应数组位置的节点(如果为null直接new一个节点,否则对该节点添加value)
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // 3.数组正在扩容,该线程也帮助扩容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            // 4.对节点添加值
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            // 4.1 链表结构
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                // 4.1.1在对应位置替换value
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                // 4.1.2直接new node
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        // 4.2 树形结构
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            // 4.2.0 转化为树形结构进行put
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

 

<think>我们正在讨论ConcurrentHashMapJDK1.8中的实现原理。在JDK1.8中,ConcurrentHashMap进行了重大改进,放弃了分段锁(Segment)的设计,转而采用更细粒度的桶(bucket)级别锁,并大量使用CAS操作来提升并发性能。 主要特点: 1. 数据结构:与HashMap类似,使用数组+链表/红黑树。当链表长度超过阈值(默认为8)时,链表会转换为红黑树;当红黑树节点数小于阈值(默认为6)时,会退化为链表。 2. 并发控制:使用CAS(Compare and Swap)和synchronized关键字。CAS用于无锁的更新操作(如初始化数组、更新节点等),而synchronized则用于锁定桶的头节点,这样锁的粒度更小,并发度更高。 具体实现原理: - 初始化:使用sizeCtl变量控制初始化和扩容状态,通过CAS设置sizeCtl来保证只有一个线程进行初始化。 - put操作: a. 如果数组为空,则先初始化数组(initTable)。 b. 计算key的hash值,定位到数组下标。如果该位置为空,则使用CAS尝试添加节点(如果CAS失败则重试)。 c. 如果该位置不为空(可能是链表或红黑树),则使用synchronized锁住头节点,然后进行插入或更新操作。 d. 插入后判断链表长度是否超过阈值,超过则转换为红黑树。 - get操作:无锁,因为节点的value和next都是volatile的,保证了可见性。 - size操作:使用一个CounterCell数组(类似LongAdder的分段计数)来减少竞争,提高并发性能。 下面我们通过代码来展示关键部分:</think>### ConcurrentHashMapJDK 1.8 中的实现原理详解 ConcurrentHashMapJDK 1.8 中进行了重大重构,抛弃了 JDK 1.7 的分段锁设计,采用了更高效、更细粒度的并发控制策略。以下是核心实现原理: #### 1. 底层数据结构 ```java // 核心数据结构 transient volatile Node<K,V>[] table; // 哈希表数组 private transient volatile int sizeCtl; // 控制标识符 private transient volatile CounterCell[] counterCells; // 并发计数 ``` - **数组 + 链表/红黑树**:与 HashMap 类似 - **Node 节点**:存储键值对,链表节点 - **TreeNode 节点**:红黑树节点(当链表长度 ≥ 8 时转换) - **ForwardingNode**:扩容时使用的特殊节点 #### 2. 核心并发控制机制 **CAS + synchronized 桶级锁**: ```java final V putVal(K key, V value, boolean onlyIfAbsent) { // ... for (Node<K,V>[] tab = table;;) { if (tab == null || (n = tab.length) == 0) tab = initTable(); // CAS初始化 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // CAS添加新节点 } else { synchronized (f) { // 锁定桶的头节点 // 处理链表/红黑树插入 } } } // ... } ``` - **CAS 操作**: - 初始化哈希表(`initTable()`) - 添加新节点到空桶 - 更新 sizeCtl 控制变量 - **synchronized 锁**: - 锁定桶的头节点(非空桶操作) - 锁粒度 = 单个桶(哈希槽) #### 3. 扩容机制(多线程协同) ```java private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { // 每个线程处理一段桶区间 while (advance) { // 分配任务区间 [bound, i] } // 处理桶内节点 synchronized (f) { // 将节点迁移到新表 } // 设置ForwardingNode标识 setTabAt(tab, i, fwd); } ``` - **并行扩容**: - 线程协助迁移:当线程访问到 ForwardingNode 时参与扩容 - 任务分割:将哈希表划分为多个区间,不同线程处理不同区间 - **无锁查询**: - 扩容期间读操作无需加锁 - 通过 ForwardingNode 访问新表 #### 4. 计数机制(LongAdder 风格) ```java final long sumCount() { CounterCell[] as = counterCells; long sum = baseCount; if (as != null) { for (CounterCell a : as) { if (a != null) sum += a.value; } } return sum; } ``` - **分段计数**: - `baseCount`:基础计数值 - `CounterCell[]`:分段计数器数组 - **减少竞争**: - 更新时优先尝试 CAS 更新 baseCount - 竞争激烈时使用 CounterCell 分散更新 #### 5. 红黑树优化 ```java static final class TreeBin<K,V> extends Node<K,V> { TreeNode<K,V> root; volatile TreeNode<K,V> first; volatile int lockState; // 读写锁状态 // 读锁计数 static final int WAITER = 2; // 写等待 static final int WRITER = 1; // 写锁定 } ``` - **树桶锁**: - 使用读写锁状态(lockState)控制并发 - 读操作共享锁,写操作独占锁 - **链表化优化**: - 扩容时如果树节点 ≤ 6,退化为链表 ### 性能优势对比 JDK 1.7 | 特性 | JDK 1.7(分段锁) | JDK 1.8(CAS + synchronized) | |--------------|--------------------------|-------------------------------| | 锁粒度 | 段级别(默认16段) | 桶级别(哈希槽) | | 并发度 | 最大=段数 | 最大=桶数 | | 扩容机制 | 单线程扩容 | 多线程协同扩容 | | 计数实现 | 全局锁更新 | LongAdder 风格分段计数 | | 数据结构 | 数组+链表 | 数组+链表/红黑树 | | 内存消耗 | 较高(段对象开销) | 较低 | ### 关键设计思想图解 ```mermaid graph TD A[put操作] --> B{桶是否为空?} B -->|是| C[CAS添加新节点] B -->|否| D[synchronized锁桶头节点] D --> E{桶结构} E -->|链表| F[链表插入] E -->|红黑树| G[树插入] F --> H{长度≥8?} H -->|是| I[转为红黑树] G --> J[维护树结构] K[get操作] --> L[无锁访问] L --> M{桶结构} M -->|链表| N[遍历链表] M -->|红黑树| O[树查找] P[扩容] --> Q[设置ForwardingNode] Q --> R[多线程迁移数据] R --> S[完成扩容] ``` ### 总结 JDK 1.8ConcurrentHashMap 通过以下创新实现高并发: 1. **细粒度锁**:桶级别锁(synchronized)替代分段锁 2. **CAS 优化**:无锁操作处理空桶添加和初始化 3. **协同扩容**:多线程并行迁移数据 4. **分段计数**:LongAdder 风格减少计数竞争 5. **读写分离**:红黑树桶使用读写状态控制 这种设计在保持线程安全的同时,大幅提升了并发性能,特别适合高并发场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值