本文最后更新于 2026-01-08,文章内容可能已经过时。

Java JUC包中的ConcurrentHashMap(JDK 1.8实现,基于CAS+synchronized+红黑树)适用于读写均衡的高并发场景(如缓存、实时计数器),提供高吞吐量和弱一致性;CopyOnWriteArrayList采用写时复制机制,适合读多写少的场景(如事件监听器、配置管理),实现无锁读但写操作需复制数组导致内存开销大。选型核心依据是读写比例:读写均衡优先ConcurrentHashMap,读多写少(≥10:1)优先CopyOnWriteArrayList,避免过度使用导致性能问题。

一、JUC包概述

Java JUC (Java Util Concurrent) 是Java平台标准版(JDK)的核心并发工具包,包含在java.util.concurrent包中。它提供了丰富的并发API和工具类,用于简化多线程环境下的并发编程,并确保程序在高并发场景下的正确性和性能。

JUC的核心模块包括:

  • 原子类(Atomic):基于CAS的无锁并发操作(如AtomicInteger)
  • 锁与同步器(Locks):如ReentrantLock、AQS(AbstractQueuedSynchronizer)
  • 并发容器(Collections):如ConcurrentHashMap、CopyOnWriteArrayList
  • 线程池(Executors):ThreadPoolExecutor、ForkJoinPool
  • 同步工具(Tools):CountDownLatch、CyclicBarrier、Semaphore
  • 并发流与Future:如CompletableFuture、ForkJoinTask

下面将深度解析两个核心并发容器:ConcurrentHashMap和CopyOnWriteArrayList。


二、ConcurrentHashMap

1. 源码实现原理

JDK 1.7 vs JDK 1.8

特性JDK 1.7JDK 1.8
锁机制分段锁(Segment)CAS + synchronized + 红黑树
锁粒度每个Segment独立加锁锁粒度更细,单个桶(bucket)加锁
扩容机制扩容时整个Segment锁住渐进式扩容,不锁整个表
数据结构数组 + 链表数组 + 链表/红黑树

JDK 1.8实现原理

  1. 数据结构:基于数组+链表/红黑树的结构
  2. 锁机制
    • 采用Node数组存储数据
    • 每个数组元素(桶)是一个链表头节点
    • 对桶的访问通过synchronized锁住桶头节点
    • 当链表长度≥8时,将链表转换为红黑树
  3. 扩容机制
    • 渐进式扩容:不是一次性锁住整个表
    • 分阶段进行:先创建新数组,然后逐步将旧数组元素迁移到新数组
    • 迁移过程中允许读操作
    • 通过sizeCtl控制扩容过程

扩容过程

  1. 为新的容量分配一个新的数组
  2. 将旧数组中的元素逐个搬移到新数组中
  3. 搬移过程中依然允许读操作
  4. 完成扩容后,将新数组替换为当前数组

扩容示意图

初始状态:[桶0][桶1][桶2][桶3]...
扩容中:[桶0][桶1][桶2][桶3]... [新桶0][新桶1][新桶2][新桶3]...
完成:[新桶0][新桶1][新桶2][新桶3]...

2. 适用场景

1. 高并发缓存系统

  • 会话存储、用户信息缓存
  • 读多写少的缓存场景(如Redis缓存的Java本地实现)

2. 实时计数器

  • API调用统计
  • 广告点击率统计
  • 交易量统计

3. 状态管理

  • 系统状态维护(如服务可用性状态)
  • 配置信息的实时更新与查询

4. 数据分片处理

  • 日志分析中的数据分片
  • 大数据处理中的任务分配

3. 代码示例

基本用法

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        // 创建ConcurrentHashMap
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        
        // 插入数据
        map.put("a", 1);
        map.putIfAbsent("a", 2); // 不会覆盖已存在的值
        
        // 原子计算
        map.compute("a", (k, v) -> v + 1); // a=2
        
        // 合并计数器
        map.merge("count", 1, Integer::sum);
        
        // 遍历(弱一致性)
        map.forEach((k, v) -> System.out.println(k + ": " + v));
    }
}

高并发计数器实现

public class Counter {
    private final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    
    public void increment(String key) {
        map.merge(key, 1, Integer::sum);
    }
    
    public int getCount(String key) {
        return map.getOrDefault(key, 0);
    }
    
    // 多线程测试
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        int threadCount = 10;
        int incrementCount = 10000;
        
        Thread[] threads = new Thread[threadCount];
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < incrementCount; j++) {
                    counter.increment("total");
                }
            });
            threads[i].start();
        }
        
        for (Thread thread : threads) {
            thread.join();
        }
        
        System.out.println("Total count: " + counter.getCount("total"));
    }
}

嵌套ConcurrentHashMap(广告投放系统示例)

public class AdStatsManager {
    private final ConcurrentHashMap<String, ConcurrentHashMap<String, WindowStats>> messageCache = 
        new ConcurrentHashMap<>();
    
    public void updateStats(String slotId, String currentMinute, AdBehaviorDTO adBehaviorDTO) {
        messageCache.computeIfAbsent(slotId, k -> new ConcurrentHashMap<>())
                   .computeIfAbsent(currentMinute, k -> new WindowStats())
                   .addBehavior(adBehaviorDTO);
    }
    
    // WindowStats类
    public static class WindowStats {
        private int successCount = 0;
        private int totalCount = 0;
        
        public void addBehavior(AdBehaviorDTO behavior) {
            totalCount++;
            if (behavior.isSuccess()) {
                successCount++;
            }
        }
        
        public double getSuccessRate() {
            return totalCount == 0 ? 0 : (double) successCount / totalCount;
        }
    }
    
    // AdBehaviorDTO类
    public static class AdBehaviorDTO {
        private boolean success;
        
        public AdBehaviorDTO(boolean success) {
            this.success = success;
        }
        
        public boolean isSuccess() {
            return success;
        }
    }
}

4. 优缺点分析

优点

  • 读操作无锁,性能高
  • 写操作通过细粒度锁保证并发安全
  • 渐进式扩容,避免性能骤降
  • JDK 1.8优化后性能显著提升

缺点

  • 弱一致性:迭代器不反映实时数据
  • 内存占用高:红黑树、计数器分片带来开销
  • 扩容成本:数据迁移可能暂时阻塞写操作

注意事项

  • 确保key的hashCode()和equals()正确实现
  • 合理设置初始容量和负载因子,减少扩容次数
  • 避免跨桶的复合操作导致死锁(尽管概率低)

三、CopyOnWriteArrayList

1. 源码实现原理

写时复制(Copy-On-Write)机制

  • 读操作直接访问当前数组,无需同步
  • 写操作(add、set、remove)时:
    1. 加锁(ReentrantLock)
    2. 复制底层数组
    3. 在新数组上执行修改
    4. 替换volatile数组引用

底层实现细节

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private transient volatile Object[] array; // 底层数组
    
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    // 读操作
    public E get(int index) {
        return getArray()[index];
    }
}

2. 适用场景

核心条件:数据读取频率远高于写入频率(读写比例≥10:1)

1. 事件监听器列表

  • GUI事件系统:监听器注册后极少修改,但事件触发频繁读取
  • 事件总线:事件处理器注册后很少变动,但事件触发频繁

2. 配置管理

  • 白名单/黑名单管理:配置加载后极少更新,但高频读取
  • 系统参数配置:配置文件读入后很少修改,但频繁查询

3. 缓存快照

  • 临时缓存:需要提供只读视图,允许短暂的数据延迟
  • 状态快照:系统状态的临时快照,用于监控或日志记录

4. 读多写少的集合

  • 系统常量列表:如国家代码列表、货币代码列表
  • 只读数据集合:如字典、词典

3. 代码示例

基本操作

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.Iterator;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        // 初始化
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        
        // 添加元素(写操作触发复制)
        list.add("Java");
        list.add("Python");
        
        // 读取元素(无锁)
        System.out.println(list.get(0)); // 输出 "Java"
        
        // 迭代器基于快照遍历
        Iterator<String> it = list.iterator();
        list.add("C++"); // 修改列表
        
        // 迭代器只输出Java、Python,不包含C++
        while (it.hasNext()) {
            System.out.println(it.next()); 
        }
    }
}

多线程安全示例

import java.util.concurrent.CopyOnWriteArrayList;

public class ThreadSafeListExample {
    private final CopyOnWriteArrayList<Integer> dataCache = new CopyOnWriteArrayList<>();
    
    public void addData(int newData) {
        dataCache.add(newData);
    }
    
    public void printData() {
        for (int num : dataCache) {
            System.out.println("Read: " + num);
        }
    }
    
    public static void main(String[] args) {
        ThreadSafeListExample example = new ThreadSafeListExample();
        
        // 读线程(可并发执行)
        Thread readerThread = new Thread(() -> {
            while (true) {
                example.printData();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        // 写线程(低频触发)
        Thread writerThread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                example.addData(i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        readerThread.start();
        writerThread.start();
    }
}

4. 优缺点分析

优点

  • 无锁读:读操作无需同步,性能远超synchronizedList或Vector
  • 线程安全:写操作通过复制机制避免数据竞争
  • 迭代安全:迭代器遍历期间,即使其他线程修改列表,也不会抛出ConcurrentModificationException

缺点

  • 内存开销大:写操作时存在临时内存翻倍,频繁修改可能导致GC压力
  • 数据可能过时:读操作看到的是修改前的快照,不是最新数据
  • 写操作性能低:每次写操作都需要复制整个数组

适用场景总结

场景说明读写比例
事件监听器列表监听器注册后极少修改,但事件触发频繁读取≥10:1
配置管理配置加载后极少更新,但高频读取≥10:1
缓存快照缓存数据的只读视图,允许短暂的数据延迟≥10:1
系统常量列表如国家代码、货币代码,加载后很少修改≥10:1

四、其他重要JUC并发容器简介

1. ConcurrentHashMap vs CopyOnWriteArrayList

特性ConcurrentHashMapCopyOnWriteArrayList
读写模式读写并发读无锁,写复制
数据一致性弱一致性读操作看到的是快照
适用场景高并发读写读多写少
内存开销中等高(写时复制)
读性能极高
写性能中等低(复制数组)
迭代器弱一致性基于快照

2. ForkJoinPool

专为分治算法设计的线程池,核心优势是工作窃取算法:

  • 任务分解:将大任务递归拆分为小任务
  • 工作窃取:空闲线程从其他线程队列尾部窃取任务
  • 适用场景:计算密集型任务,如大数据分析、图像处理

3. ConcurrentLinkedQueue

基于CAS的无锁队列,适用于高并发场景:

ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("item");
String item = queue.poll();

五、总结与选型建议

1. 选择并发容器的决策树

  1. 如果需要高并发读写

    • 读写比例高(>10:1) → CopyOnWriteArrayList
    • 读写比例均衡 → ConcurrentHashMap
    • 计算密集型任务 → ForkJoinPool
  2. 如果需要线程安全的列表

    • 读多写少 → CopyOnWriteArrayList
    • 读写均衡 → Collections.synchronizedList()
    • 需要阻塞队列 → BlockingQueue
  3. 如果需要线程安全的Map

    • 读多写少 → ConcurrentHashMap
    • 读写均衡 → Collections.synchronizedMap()
    • 需要有序性 → ConcurrentSkipListMap

2. 最佳实践

  1. 合理设置初始容量:避免频繁扩容
  2. 选择合适的并发容器:根据业务场景选择,不要过度使用
  3. 避免在循环中频繁修改:对于CopyOnWriteArrayList,写操作成本高
  4. 注意弱一致性:理解ConcurrentHashMap的弱一致性特性
  5. 性能测试:在实际场景中测试不同容器的性能

3. 源码学习建议

  1. 从简单开始:先理解AtomicInteger的CAS实现
  2. 逐步深入:研究AQS(AbstractQueuedSynchronizer)框架
  3. 对比版本:比较JDK 1.7和1.8中ConcurrentHashMap的差异
  4. 实际应用:在项目中尝试使用这些并发容器,观察性能变化
  5. 源码调试:使用IDE调试JUC源码,理解关键方法执行流程

六、结论

Java JUC包提供了丰富的并发工具,其中ConcurrentHashMap和CopyOnWriteArrayList是两个非常重要的并发容器。理解它们的实现原理、适用场景和性能特点,对编写高性能、线程安全的Java程序至关重要。

  • ConcurrentHashMap适用于读写均衡的高并发场景,是缓存、计数器等应用的首选
  • CopyOnWriteArrayList适用于读多写少的场景,能提供极高的读性能,但写操作成本较高

在实际项目中,应根据具体业务场景和性能需求,选择合适的并发容器,避免过度使用或错误使用,从而实现最佳的性能和可维护性。