Java并发容器深度解析

一、ConcurrentHashMap:并发哈希表的演进

1.1 JDK 7:分段锁(Segment)机制

// JDK 7 核心结构
final Segment<K,V>[] segments; // 16个段默认

static final class Segment<K,V> extends ReentrantLock {
    transient volatile HashEntry<K,V>[] table; // 每个段的哈希表
    transient int count; // 段内元素个数
}

核心特点

  • 锁分段:将哈希表分为16个Segment,每个Segment独立加锁
  • 写操作:只锁定当前Segment,其他Segment可并发访问
  • 读操作:无锁(volatile保证可见性)
  • 缺点:Segment数组大小固定,最多支持16个并发写

put操作伪代码

public V put(K key, V value) {
    int hash = hash(key);
    Segment<K,V> s = segmentForHash(hash); // 定位到Segment
    s.lock(); // 只锁当前段
    try {
        // 在段内执行put...
    } finally {
        s.unlock();
    }
}

1.2 JDK 8+:CAS + synchronized + 红黑树

// JDK 8+ 核心结构
transient volatile Node<K,V>[] table; // Node数组
transient volatile int sizeCtl; // 控制标志位

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
}

革命性改进

  • 锁粒度细化:每个Node头节点作为锁(synchronized)
  • CAS无锁化:初始化、扩容等关键步骤使用CAS
  • 红黑树优化:链表长度>8转红黑树,提升查询性能至O(logn)

put操作源码分析

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;
        
        // 1. 初始化:CAS保证单线程创建table
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        
        // 2. 定位桶:CAS获取头节点
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break; // 无竞争直接插入成功
        }
        
        // 3. 协助扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        
        // 4. 同步块:只锁单个Node
        else {
            V oldVal = null;
            synchronized (f) { // 细化到桶级别
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) { // 链表
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash && ((ek = e.key) == key || ...)) {
                                oldVal = e.val;
                                if (!onlyIfAbsent) e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) { // 红黑树
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent) p.val = value;
                        }
                    }
                }
            }
            
            // 5. 链表转红黑树
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null) return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

1.3 关键差异对比

特性JDK 7 SegmentJDK 8+ Node + CAS
锁粒度16个Segment每个Node头节点
并发度最多16理论无限制(数组大小)
查询复杂度O(n) 链表O(logn) 红黑树
扩容机制Segment独立扩容多线程协助扩容
内存占用较高(Segment对象)更轻量
适用场景低并发写入高并发读写

二、CopyOnWriteArrayList:写时复制容器

2.1 核心原理

public class CopyOnWriteArrayList<E> implements List<E> {
    private transient volatile Object[] array; // volatile保证可见性
    
    final Object[] getArray() { return array; }
    final void setArray(Object[] a) { array = a; }
}

设计哲学读操作无锁,写操作复制新数组

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); // volatile写,立即可见
        return true;
    } finally {
        lock.unlock();
    }
}

public E get(int index) {
    return get(getArray(), index); // 读操作无锁,直接访问volatile数组
}

2.2 性能特征分析

优势

  • 读性能极高:无锁,接近普通ArrayList
  • 迭代安全:遍历的是快照,不受写操作影响
  • 内存一致性:volatile保证立即可见

代价

  • 写性能差:每次复制整个数组,O(n)复杂度
  • 内存占用高:同时存在两个数组副本
  • 数据延迟:读操作可能读取到旧数据(弱一致性)

2.3 适用场景与陷阱

正确场景

// 事件监听器列表(读远多于写)
private final CopyOnWriteArrayList<EventListener> listeners = 
    new CopyOnWriteArrayList<>();

public void addListener(EventListener listener) {
    listeners.add(listener); // 写少
}

public void fireEvent(Event event) {
    for (EventListener listener : listeners) { // 读多,无锁
        listener.onEvent(event);
    }
}

错误场景

// 高频写入场景!绝对避免!
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100000; i++) {
    list.add(i); // 每次复制,内存和CPU爆炸
}

三、BlockingQueue:阻塞队列家族

3.1 接口定义与核心方法

public interface BlockingQueue<E> extends Queue<E> {
    // 阻塞方法
    void put(E e) throws InterruptedException; // 队列满时阻塞
    E take() throws InterruptedException;      // 队列空时阻塞
    
    // 超时方法
    boolean offer(E e, long timeout, TimeUnit unit);
    E poll(long timeout, TimeUnit unit);
    
    // 非阻塞方法
    boolean offer(E e); // 失败立即返回false
    E poll();           // 失败立即返回null
}

3.2 主要实现类对比

实现类底层结构容量锁机制适用场景
ArrayBlockingQueue数组有界全局ReentrantLock固定大小缓冲区
LinkedBlockingQueue链表可选有界双锁(put/take分离)高并发吞吐量
PriorityBlockingQueue无界全局ReentrantLock优先级任务调度
SynchronousQueue无存储0容量CAS/TransferQueue直接传递,线程配对
DelayQueuePriorityQueue无界全局ReentrantLock延迟任务调度
LinkedTransferQueue链表无界CAS + 自旋高性能传输

3.3 核心实现剖析

LinkedBlockingQueue:双锁分离设计
public class LinkedBlockingQueue<E> {
    private final AtomicInteger count = new AtomicInteger(); // 原子计数
    
    private final ReentrantLock takeLock = new ReentrantLock(); // 消费锁
    private final Condition notEmpty = takeLock.newCondition();
    
    private final ReentrantLock putLock = new ReentrantLock(); // 生产锁
    private final Condition notFull = putLock.newCondition();
    
    public void put(E e) throws InterruptedException {
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        
        putLock.lockInterruptibly(); // 只获取put锁
        try {
            while (count.get() == capacity) {
                notFull.await(); // 队列满时等待
            }
            enqueue(node); // 入队
            c = count.getAndIncrement(); // 原子计数+1
            if (c + 1 < capacity) notFull.signal(); // 唤醒生产者
        } finally {
            putLock.unlock();
        }
        
        // 关键:c==0表示队列由空变非空,唤醒消费者
        if (c == 0) signalNotEmpty();
    }
}

性能优势:put和take操作使用不同锁,吞吐量比ArrayBlockingQueue高2-3倍。


SynchronousQueue:零容量队列
// 线程配对传输,无存储空间
SynchronousQueue<Integer> queue = new SynchronousQueue<>();

// 线程A:put会阻塞直到有线程take
new Thread(() -> {
    try {
        queue.put(1); // 等待消费者
    } catch (InterruptedException e) {}
}).start();

// 线程B:take会阻塞直到有线程put
new Thread(() -> {
    try {
        Integer value = queue.take(); // 立即获得1
    } catch (InterruptedException e) {}
}).start();

底层实现:使用TransferStack/TransferQueue算法,通过CAS实现线程配对,性能极高。


3.4 实战:生产者-消费者模式

public class DataProcessor {
    private final BlockingQueue<Task> queue = 
        new LinkedBlockingQueue<>(100); // 有界缓冲
    
    // 生产者
    public void produce(Task task) {
        boolean submitted = queue.offer(task); // 非阻塞
        if (!submitted) {
            // 队列满,降级处理
            log.warn("Queue full, dropping task: {}", task);
            // 或执行背压策略
            // handleBackPressure(task);
        }
    }
    
    // 消费者(多线程)
    public void consume() {
        while (running) {
            try {
                Task task = queue.poll(1, TimeUnit.SECONDS);
                if (task != null) {
                    process(task); // 处理任务
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

// 优雅关闭
public void shutdown() throws InterruptedException {
    running = false;
    // 等待队列消费完毕
    while (!queue.isEmpty()) {
        Thread.sleep(100);
    }
    executor.shutdown();
    executor.awaitTermination(60, TimeUnit.SECONDS);
}

四、选型指南与最佳实践

4.1 并发Map选型

// 高并发读写
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();

// 写极少,读极多(配置类)
Map<String, String> config = new ConcurrentHashMap<>();
// 或不可变Map
Map<String, String> immutableConfig = Map.copyOf(initialMap);

// 统计计数
ConcurrentHashMap<String, LongAdder> counter = new ConcurrentHashMap<>();
counter.computeIfAbsent(key, k -> new LongAdder()).increment();

4.2 并发List选型

// 读多写少(事件监听)
CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();

// 读写均衡:Collections.synchronizedList + 手动同步
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

// 高性能无锁读:immutableList + volatile
volatile List<String> cachedList = Collections.emptyList();

public void updateList(List<String> newList) {
    this.cachedList = List.copyOf(newList); // 原子更新
}

4.3 队列选型决策树

需要阻塞?
├─ 是 → 需要延迟/优先级?
│   ├─ 延迟 → DelayQueue
│   ├─ 优先级 → PriorityBlockingQueue
│   └─ 普通 → 需要直接传递?
│       ├─ 是 → SynchronousQueue
│       └─ 否 → 有界?ArrayBlockingQueue : LinkedBlockingQueue
└─ 否 → 需要并发安全?
    ├─ 是 → ConcurrentLinkedQueue
    └─ 否 → ArrayDeque/LinkedList

五、性能陷阱与规避

5.1 ConcurrentHashMap陷阱

// ❌ 错误:复合操作非原子
if (map.get(key) == null) {
    map.put(key, computeValue()); // 可能覆盖其他线程的值
}

// ✅ 正确:使用原子方法
map.computeIfAbsent(key, k -> computeValue());

// ❌ 错误:遍历同时修改会抛异常
for (String key : map.keySet()) {
    map.remove(key); // ConcurrentModificationException
}

// ✅ 正确:使用迭代器或并发方法
map.keySet().removeIf(k -> shouldRemove(k));

5.2 CopyOnWriteArrayList陷阱

// ❌ 错误:高频修改
for (int i = 0; i < 1000; i++) {
    list.add(i); // 内存爆炸!
}

// ✅ 正确:批量修改
List<Integer> temp = new ArrayList<>(list);
temp.addAll(batchData);
list = new CopyOnWriteArrayList<>(temp); // 一次性替换

// ❌ 错误:依赖实时一致性
if (list.size() > 0) {
    // size可能已变,但迭代是安全的
    list.forEach(System.out::println);
}

总结

容器核心机制适用场景禁用场景
ConcurrentHashMapCAS + synchronized高并发读写强一致性复合操作
CopyOnWriteArrayList写时复制读多写少(监听列表)高频修改、大数据量
BlockingQueue锁/Condition生产者-消费者无界队列+高速生产

金句:并发容器没有银弹,理解其内部机制是正确选型的前提。在性能敏感场景,务必通过JMH压测验证选择。

更多推荐