关于ConcurrentHashMap的深刻理解

1.介绍ConcurrentHashMap

  ConcurrentHashMap 是 Java 中一个线程安全且高效的哈希表实现,它位于 java.util.concurrent 包下,在多线程环境下可以替代 HashTable 和同步包装器 Collections.synchronizedMap。以下将详细介绍 ConcurrentHashMap 的底层原理和常用使用方法。

2.底层原理

1. Java 7 版本

在 Java 7 中,ConcurrentHashMap 采用分段锁(Segment)机制来实现线程安全。

  • 数据结构ConcurrentHashMap 内部包含一个 Segment 数组,每个 Segment 类似于一个小的 HashMap,它继承自 ReentrantLock。每个 Segment 维护着一个 HashEntry 数组,HashEntry 是一个链表节点,用于存储键值对。
  • 分段锁机制:不同的 Segment 可以被不同的线程同时访问,因此在多线程环境下,只要访问的是不同的 Segment,就不会产生锁竞争,从而提高了并发性能。只有当多个线程访问同一个 Segment 时,才需要竞争该 Segment 上的锁。

2. Java 8 版本

Java 8 对 ConcurrentHashMap 进行了重大改进,摒弃了分段锁机制,采用 CAS(Compare-And-Swap)和 synchronized 来保证并发安全。

  • 数据结构ConcurrentHashMap 采用数组 + 链表 + 红黑树的结构。当链表长度超过一定阈值(默认为 8)且数组长度大于 64 时,链表会转换为红黑树,以提高查找效率。
  • CAS 和 synchronized
    • CAS:在插入或更新元素时,首先使用 CAS 操作尝试直接修改数组中的节点,如果操作成功,则表示插入或更新成功;如果失败,则说明有其他线程在同时操作,此时需要使用 synchronized 进行同步。
    • synchronized:当需要对某个节点进行修改时,会使用 synchronized 对该节点进行加锁,保证同一时刻只有一个线程可以对该节点进行操作。

3.常用使用方法

1. 初始化 ConcurrentHashMap

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        // 初始化一个空的 ConcurrentHashMap
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    }
}

2. 添加元素

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        // 添加单个元素
        map.put("apple", 1);
        // 如果指定的键不存在,则添加该键值对
        map.putIfAbsent("banana", 2);
    }
}

3. 获取元素

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        map.put("apple", 1);
        // 根据键获取值
        Integer value = map.get("apple");
        System.out.println(value); // 输出: 1
    }
}

4. 删除元素

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        map.put("apple", 1);
        // 根据键删除元素
        Integer removedValue = map.remove("apple");
        System.out.println(removedValue); // 输出: 1
    }
}

5. 遍历元素

import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);

        // 使用 for-each 循环遍历键值对
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

6. 统计元素数量

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        // 获取元素数量
        int size = map.size();
        System.out.println(size); // 输出: 2
    }
}

4.ConcurrenntHashMap在java8中具体做了哪些优化

在 Java 8 中,ConcurrentHashMap 相较于 Java 7 版本有了重大的优化,下面从数据结构、锁机制、并发操作等方面详细介绍这些优化点。

1. 数据结构优化

  • 摒弃分段锁,采用数组 + 链表 + 红黑树
    • Java 7:使用分段锁(Segment)机制,ConcurrentHashMap 内部是一个 Segment 数组,每个 Segment又包含一个 HashEntry 数组,本质上相当于多个小的 HashMap
    • Java 8:采用数组 + 链表 + 红黑树的结构。数组用于存储节点,当发生哈希冲突时,相同哈希值的节点会以链表的形式存储在数组的对应位置。当链表长度超过一定阈值(默认为 8)且数组长度大于 64 时,链表会转换为红黑树;当红黑树节点数量小于等于 6 时,红黑树会转换回链表。这种结构在处理哈希冲突时,尤其是在链表较长的情况下,能显著提高查找、插入和删除操作的效率,因为红黑树的时间复杂度为 O(log n),而链表为 O(n)。

2. 锁机制优化

  • 使用 CAS 和 synchronized 替代分段锁
    • Java 7:使用分段锁,不同的 Segment 可以被不同的线程同时访问,只有访问同一个 Segment 时才会产生锁竞争。但这种方式在某些场景下可能会导致锁的粒度较大,影响并发性能。
    • Java 8:采用 CAS(Compare-And-Swap)和 synchronized 来保证并发安全。
      • CAS:在插入或更新元素时,首先使用 CAS 操作尝试直接修改数组中的节点。CAS 是一种无锁算法,通过比较内存中的值和预期值是否相等来决定是否更新,操作是原子性的。如果操作成功,则表示插入或更新成功;如果失败,则说明有其他线程在同时操作,此时需要使用 synchronized 进行同步。
      • synchronized:当需要对某个节点进行修改时,会使用 synchronized 对该节点进行加锁,保证同一时刻只有一个线程可以对该节点进行操作。synchronized 在 Java 8 中进行了大量优化,性能有了显著提升,而且锁的粒度更小,只对单个节点加锁,相比 Java 7 的分段锁,并发性能得到了进一步提高。

3. 并发操作优化

  • 支持并发的 computecomputeIfAbsent 和 computeIfPresent 等方法
    • compute 方法:允许根据键的当前值(如果存在)和给定的计算函数来计算并更新值。这个方法在多线程环境下可以安全地执行更新操作,避免了先检查后更新带来的并发问题。
    • computeIfAbsent 方法:如果指定的键不存在,则使用给定的函数计算一个值,并将其插入到 ConcurrentHashMap 中。这个方法可以避免在多线程环境下重复计算相同键的值。
    • computeIfPresent 方法:如果指定的键存在,则使用给定的函数计算一个新值,并更新该键对应的值。

4. 遍历操作优化

  • 弱一致性迭代器:Java 8 的 ConcurrentHashMap 提供了弱一致性的迭代器,在迭代过程中,即使其他线程对 ConcurrentHashMap 进行了修改,迭代器也能继续工作,不会抛出 ConcurrentModificationException。这是因为迭代器在创建时会记录当前 ConcurrentHashMap 的快照,在迭代过程中会尽量反映出创建时的状态,同时也会尝试反映后续的修改,但不保证完全一致。这种弱一致性的设计在多线程环境下提供了更好的并发性能。

以下是一个使用 computeIfAbsent 方法的示例代码:

java

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapComputeIfAbsentExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        // 如果键 "apple" 不存在,则计算值并插入
        Integer value = map.computeIfAbsent("apple", k -> 1);
        System.out.println(value); // 输出: 1
    }
}

综上所述,Java 8 的 ConcurrentHashMap 通过数据结构、锁机制、并发操作和遍历操作等方面的优化,提高了并发性能和使用的便利性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值