编辑
2025-10-15
Java
00
请注意,本文编写于 51 天前,最后修改于 46 天前,其中某些信息可能已经过时。

目录

ConcurrentHashMap概述
主要特性
基本使用方法
1. 创建ConcurrentHashMap
2. 基本操作
高级操作方法
1. 原子更新操作
2. 批量操作
线程安全实践
1. 多线程读写示例
2. 缓存实现示例
与其它Map的对比
性能比较
最佳实践
1. 选择合适的初始容量
2. 使用合适的并发级别
3. 避免在遍历时修改结构
4. 使用原子操作复合操作
常见问题与解决方案
1. 内存一致性问题
2. 迭代器弱一致性
总结

在多线程编程中,HashMap是非线程安全的,而Hashtable虽然是线程安全的,但性能较差。ConcurrentHashMap应运而生,它提供了更好的并发性能和线程安全性。本文将详细介绍ConcurrentHashMap的使用方法。

ConcurrentHashMap概述

ConcurrentHashMap是Java并发包(java.util.concurrent)中的一个线程安全的HashMap实现。与Hashtable不同,它使用分段锁(Java 7)或CAS操作(Java 8+)来实现更高的并发性能。

主要特性

  • 线程安全:多个线程可以同时读写而不会导致数据不一致
  • 高并发性能:读操作通常不需要加锁,写操作使用细粒度锁
  • 弱一致性:迭代器不会抛出ConcurrentModificationException
  • 不支持null键和null值

基本使用方法

1. 创建ConcurrentHashMap

java
import java.util.concurrent.ConcurrentHashMap; // 创建空的ConcurrentHashMap ConcurrentHashMap<String, Integer> map1 = new ConcurrentHashMap<>(); // 创建指定初始容量的ConcurrentHashMap ConcurrentHashMap<String, Integer> map2 = new ConcurrentHashMap<>(32); // 从现有Map创建 Map<String, Integer> existingMap = new HashMap<>(); existingMap.put("A", 1); existingMap.put("B", 2); ConcurrentHashMap<String, Integer> map3 = new ConcurrentHashMap<>(existingMap); // 创建指定初始容量和负载因子的ConcurrentHashMap ConcurrentHashMap<String, Integer> map4 = new ConcurrentHashMap<>(16, 0.75f); // 创建指定初始容量、负载因子和并发级别的ConcurrentHashMap ConcurrentHashMap<String, Integer> map5 = new ConcurrentHashMap<>(16, 0.75f, 8);

2. 基本操作

java
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>(); // 添加元素 concurrentMap.put("apple", 10); concurrentMap.put("banana", 20); concurrentMap.put("orange", 15); // 如果键不存在则添加 concurrentMap.putIfAbsent("apple", 50); // 不会执行,因为"apple"已存在 concurrentMap.putIfAbsent("grape", 30); // 会添加,因为"grape"不存在 // 获取元素 Integer apples = concurrentMap.get("apple"); // 返回10 Integer unknown = concurrentMap.get("unknown"); // 返回null // 检查键是否存在 boolean hasApple = concurrentMap.containsKey("apple"); // true boolean hasMango = concurrentMap.containsKey("mango"); // false // 删除元素 concurrentMap.remove("banana"); // 删除键为"banana"的条目 concurrentMap.remove("orange", 10); // 只有当键为"orange"且值为10时才删除 // 获取大小 int size = concurrentMap.size(); // 检查是否为空 boolean isEmpty = concurrentMap.isEmpty();

高级操作方法

1. 原子更新操作

java
ConcurrentHashMap<String, Integer> inventory = new ConcurrentHashMap<>(); inventory.put("widget", 100); // compute方法:根据现有键值对计算新值 inventory.compute("widget", (key, value) -> value == null ? 1 : value + 50); // 现在widget的值为150 // computeIfAbsent:如果键不存在,计算新值 inventory.computeIfAbsent("gadget", key -> 200); // gadget不存在,所以设置为200 // computeIfPresent:如果键存在,计算新值 inventory.computeIfPresent("widget", (key, value) -> value - 10); // widget存在,所以值变为140 // merge方法:合并值 inventory.merge("widget", 50, (oldValue, newValue) -> oldValue + newValue); // widget的值变为190 (140 + 50)

2. 批量操作

java
ConcurrentHashMap<String, String> userSessions = new ConcurrentHashMap<>(); userSessions.put("user1", "session1"); userSessions.put("user2", "session2"); userSessions.put("user3", "session3"); // forEach:遍历所有条目 userSessions.forEach((key, value) -> System.out.println("User: " + key + ", Session: " + value)); // forEach带并行阈值 userSessions.forEach(2, (key, value) -> System.out.println("Processing: " + key)); // reduce:归约操作 ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>(); scores.put("Alice", 85); scores.put("Bob", 92); scores.put("Charlie", 78); int totalScore = scores.reduce(2, (key, value) -> value, Integer::sum); System.out.println("Total score: " + totalScore); // search:搜索操作 String found = scores.search(2, (key, value) -> value > 90 ? key : null); System.out.println("Score > 90: " + found); // 输出: Bob

线程安全实践

1. 多线程读写示例

java
public class ConcurrentHashMapExample { private final ConcurrentHashMap<String, AtomicInteger> counterMap = new ConcurrentHashMap<>(); // 线程安全的计数方法 public void increment(String key) { counterMap.compute(key, (k, v) -> { if (v == null) { return new AtomicInteger(1); } else { v.incrementAndGet(); return v; } }); } // 或者使用更简洁的方式 public void incrementBetter(String key) { counterMap.computeIfAbsent(key, k -> new AtomicInteger(0)).incrementAndGet(); } public int getCount(String key) { AtomicInteger counter = counterMap.get(key); return counter == null ? 0 : counter.get(); } }

2. 缓存实现示例

java
public class SimpleCache<K, V> { private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>(); private final long defaultExpiry; public SimpleCache(long defaultExpiryMillis) { this.defaultExpiry = defaultExpiryMillis; } public V get(K key) { return cache.get(key); } public void put(K key, V value) { cache.put(key, value); } public V getOrCompute(K key, Supplier<V> supplier) { return cache.computeIfAbsent(key, k -> supplier.get()); } public boolean remove(K key) { return cache.remove(key) != null; } public void clear() { cache.clear(); } }

与其它Map的对比

性能比较

java
public class MapComparison { public static void main(String[] args) throws InterruptedException { // 测试HashMap(非线程安全) Map<String, Integer> hashMap = new HashMap<>(); testMapPerformance(hashMap, "HashMap"); // 测试Hashtable(线程安全,但性能较差) Map<String, Integer> hashtable = new Hashtable<>(); testMapPerformance(hashtable, "Hashtable"); // 测试ConcurrentHashMap(线程安全,高性能) Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>(); testMapPerformance(concurrentHashMap, "ConcurrentHashMap"); } private static void testMapPerformance(Map<String, Integer> map, String mapType) throws InterruptedException { int threadCount = 10; int operationsPerThread = 10000; Thread[] threads = new Thread[threadCount]; long startTime = System.currentTimeMillis(); for (int i = 0; i < threadCount; i++) { final int threadId = i; threads[i] = new Thread(() -> { for (int j = 0; j < operationsPerThread; j++) { String key = "key-" + threadId + "-" + j; map.put(key, j); map.get(key); } }); threads[i].start(); } for (Thread thread : threads) { thread.join(); } long endTime = System.currentTimeMillis(); System.out.println(mapType + " 耗时: " + (endTime - startTime) + "ms"); } }

最佳实践

1. 选择合适的初始容量

java
// 如果知道大概的元素数量,设置合适的初始容量可以提高性能 int expectedSize = 100000; ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>( (int)(expectedSize / 0.75f) + 1 );

2. 使用合适的并发级别

java
// 根据预期的并发线程数设置并发级别 int expectedThreads = 16; ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>( 16, 0.75f, expectedThreads );

3. 避免在遍历时修改结构

java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("A", 1); map.put("B", 2); map.put("C", 3); // 安全的遍历方式 for (String key : map.keySet()) { System.out.println(key + ": " + map.get(key)); // 在遍历过程中可以安全地修改值,但不能添加/删除键 map.put(key, map.get(key) + 1); // 安全 // map.remove(key); // 可能导致未定义行为 }

4. 使用原子操作复合操作

java
// 不安全的做法 if (!map.containsKey(key)) { map.put(key, value); // 这不是原子操作! } // 安全的做法 map.putIfAbsent(key, value); // 或者使用computeIfAbsent map.computeIfAbsent(key, k -> createExpensiveValue(k));

常见问题与解决方案

1. 内存一致性问题

java
// 错误用法:check-then-act模式 if (concurrentMap.containsKey(key)) { // 在这期间,其他线程可能已经删除了该键 Value value = concurrentMap.get(key); // 可能返回null process(value); // 可能抛出NullPointerException } // 正确用法:原子操作 Value value = concurrentMap.get(key); if (value != null) { process(value); }

2. 迭代器弱一致性

java
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("A", 1); map.put("B", 2); Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> entry = iterator.next(); System.out.println(entry.getKey() + ": " + entry.getValue()); // 其他线程在此期间修改map不会影响当前迭代 // 但也不会在迭代器中反映这些变化 }

总结

ConcurrentHashMap是Java并发编程中的重要工具,它提供了:

  • 高性能的线程安全Map实现
  • 丰富的原子操作方法
  • 良好的扩展性
  • 弱一致性的迭代器

在使用时需要注意:

  • 选择合适的初始容量和并发级别
  • 使用原子操作代替check-then-act模式
  • 理解迭代器的弱一致性特性
  • 避免在遍历时进行结构修改

通过合理使用ConcurrentHashMap,可以构建出高性能、线程安全的并发应用程序。

本文作者:zjx171

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!