ConcurrentHashMap 详细介绍

ConcurrentHashMap 是 Java 中一个线程安全的 Map 实现,它在 java.util.concurrent 包中引入于 Java 5。与 Hashtable 不同,ConcurrentHashMap 通过细粒度的锁机制来提供线程安全,允许多个线程同时访问和修改地图中的数据,极大提高了并发性能。

主要特点:
  1. 线程安全ConcurrentHashMap 确保多个线程在没有外部同步的情况下,可以安全地访问和修改数据。
  2. 分段锁(Java 7 及之前):在 Java 7 及之前的版本中,ConcurrentHashMap 将 map 分成多个段,每个段有自己的锁,允许不同线程访问不同段的内容,从而减少竞争。
  3. 锁分条(Java 8 及以后):Java 8 引入了更细粒度的锁,桶级别的锁结合 CAS(比较和交换)操作,极大提升了性能。
  4. 不允许 null 键和值:与 HashMap 不同,ConcurrentHashMap 不允许使用 null 作为键或值。
  5. 高性能:由于减少了锁的竞争,ConcurrentHashMap 在多线程环境中比 Hashtable 或同步的 HashMap 提供更好的性能。
  6. 原子操作:像 putIfAbsentcomputeIfAbsentreplace 等方法是原子操作,确保线程安全。
  7. 弱一致性迭代:迭代器是弱一致性的,在迭代过程中即使有其他线程修改了 map,迭代器也不会抛出 ConcurrentModificationException 异常。
  8. 良好的可扩展性:由于锁的粒度较小,ConcurrentHashMap 可以随着线程数的增加而更好地扩展,提供较高的吞吐量。
内部工作原理:
  • 结构ConcurrentHashMap 内部使用哈希表结构,键通过哈希运算分配到不同的桶(bucket)。如果发生哈希冲突,使用链表或者红黑树(Java 8 及以后)解决冲突。
  • 并发级别:Java 7 及以前,构造时可以指定 concurrencyLevel 参数来控制分段数。Java 8 之后,这个参数的影响变得不那么显著,ConcurrentHashMap 会动态调整桶级别的锁。
  • Java 8 改进
    • 采用桶级别的锁,通过 synchronized 块和 CAS 操作提高性能。
    • 当桶中的元素过多时,将链表转换成红黑树,从而提升查询性能(从 O(n) 降到 O(log n))。
    • 支持函数式编程,提供 forEachcomputemerge 等方法。
常见方法:
  • put(K key, V value):将指定的值与键关联,如果键已存在,则更新值。
  • putIfAbsent(K key, V value):只有当键不存在时,才插入该键值对。
  • get(K key):获取与键关联的值。
  • remove(K key):移除指定键的键值对。
  • computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction):如果键不存在,使用提供的函数计算并插入新值。
  • forEach(BiConsumer<? super K, ? super V> action):迭代所有的键值对。

ConcurrentHashMap 示例代码

下面是几个常见的使用 ConcurrentHashMap 的例子:

示例 1:多线程环境中的基本使用

这个例子展示了如何在多个线程中安全地向 ConcurrentHashMap 中添加和获取数据。

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        // 创建 ConcurrentHashMap
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 模拟多个线程向 map 中添加数据
        for (int i = 1; i <= 5; i++) {
            final int value = i;
            executor.submit(() -> {
                map.put("Key" + value, value);
                System.out.println(Thread.currentThread().getName() + " added: Key" + value + " -> " + value);
            });
        }
        
        // 模拟一个线程从 map 中读取数据
        executor.submit(() -> {
            try {
                Thread.sleep(100); // 等待一些插入操作完成
                System.out.println("Current map contents: " + map);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        // 关闭线程池
        executor.shutdown();
    }
}

解释

  • 多个线程并发地向 ConcurrentHashMap 中插入键值对。
  • 另外一个线程读取 map 中的内容。
  • ConcurrentHashMap 保证了线程安全,因此不需要外部同步机制。
示例 2:使用 putIfAbsent 进行条件插入

这个例子展示了如何使用 putIfAbsent 方法进行条件插入,仅当键不存在时才插入新值。

import java.util.concurrent.ConcurrentHashMap;

public class PutIfAbsentExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        
        // 如果键不存在,则插入键值对
        map.putIfAbsent("key1", "value1");
        System.out.println("After putIfAbsent key1: " + map); // {key1=value1}
        
        // 对同一个键进行第二次 putIfAbsent 插入
        map.putIfAbsent("key1", "newValue");
        System.out.println("After second putIfAbsent key1: " + map); // 依然是 {key1=value1}
        
        // 插入新的键值对
        map.putIfAbsent("key2", "value2");
        System.out.println("After putIfAbsent key2: " + map); // {key1=value1, key2=value2}
    }
}

解释

  • putIfAbsent 方法确保只有当键不存在时才会插入新的键值对。
  • 第二次对 key1 的插入操作不会覆盖原有的值,保证了原子性。
示例 3:使用 computeIfAbsent 进行懒加载

这个例子展示了如何使用 computeIfAbsent 方法进行懒加载,当键不存在时计算并插入值。

import java.util.concurrent.ConcurrentHashMap;

public class ComputeIfAbsentExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        
        // 如果键不存在,则计算值并插入
        map.computeIfAbsent("key1", key -> key.length());
        System.out.println("After computeIfAbsent key1: " + map); // {key1=4}
        
        // 对相同的键再次调用 computeIfAbsent,不会重新计算
        map.computeIfAbsent("key1", key -> 100);
        System.out.println("After second computeIfAbsent key1: " + map); // 依然是 {key1=4}
        
        // 为新的键计算值
        map.computeIfAbsent("key2", key -> key.length());
        System.out.println("After computeIfAbsent key2: " + map); // {key1=4, key2=4}
    }
}

解释

  • computeIfAbsent 会在键不存在时,根据提供的函数计算并插入值。
  • 对已存在的键再次调用 computeIfAbsent 时,不会重新计算值。
示例 4:并发迭代

这个例子展示了在一个线程修改 ConcurrentHashMap 的同时,另一个线程迭代其内容。

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentIterationExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        map.put("key1", 1);
        map.put("key2", 2);
        
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        // 线程 1:迭代 map
        executor.submit(() -> {
            System.out.println("Iterating over map:");
            for (var entry : map.entrySet()) {
                System.out.println(entry.getKey() + " -> " + entry.getValue());
                try {
                    Thread.sleep(100); // 模拟慢迭代
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        // 线程 2:修改 map
        executor.submit(() -> {
            try {
                Thread.sleep(50); // 等待迭代开始
                map.put("key3", 3);
                System.out.println("Added key3 -> 3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        executor.shutdown();
    }
}

解释

  • 一个线程迭代 ConcurrentHashMap,另一个线程修改 map。
  • 由于 ConcurrentHashMap 的迭代器是弱一致性的,修改操作不会抛出 ConcurrentModificationException,但是迭代器可能不会立即反映出新的条目(如 key3)。

总结

ConcurrentHashMap 是一种高效的线程安全 Map 实现,适用于高并发环境。它提供了细粒度的锁机制,使得多个线程可以安全地并发访问和修改数据。常用方法如 putIfAbsentcomputeIfAbsentforEach 提供了原子操作和懒加载的功能。

它非常适合以下场景:

  • 多线程并发访问时(例如缓存、计数等)。
  • 需要高性能的并发操作时(比 HashtablesynchronizedMap 更高效)。
  • 不需要 null 键和值时。

但在单线程或对 null 值有需求时,可能不适合使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ricky_Ribbon

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值