Java并发容器与工具库深度解析

1. ConcurrentHashMap分段演进

分段锁到CAS的进化

ConcurrentHashMap是线程安全的HashMap实现,其线程安全策略经历了重大演进:

  • JDK7分段锁设计

    // JDK7实现:16个Segment数组,每个Segment继承ReentrantLock
    static final class Segment<K,V> extends ReentrantLock {
      transient volatile HashEntry<K,V>[] table;
    }
  • JDK8 CAS+sychronized优化

    // JDK8实现:Node数组+CAS+synchronized
    final V putVal(K key, V value, boolean onlyIfAbsent) {
      if (key == null || value == null) throw new NullPointerException();
      int hash = spread(key.hashCode());
      Node<K,V>[] tab; Node<K,V> f; int n, i, fh;
      // 使用CAS初始化table
      if ((tab = table) == null || (n = tab.length) == 0)
          tab = initTable();
      // CAS访问数组元素
      else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
          if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
              break;
      }
      // synchronized锁链表头节点
      else {
          synchronized (f) {
              // 链表操作...
          }
      }
    }

性能对比

版本锁粒度并发度内存开销
JDK7段级别16(默认)较高
JDK8节点级别理论上是Node数组大小较低

实践建议

  1. 写多读少场景建议测试JDK7/8版本性能差异
  2. 初始化时预估容量避免扩容开销
  3. 使用computeIfAbsent等原子方法减少锁竞争

2. CopyOnWrite容器适用场景

实现原理

// CopyOnWriteArrayList核心代码
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();
    }
}

适用场景分析

  • 适合场景

    • 读多写少(如黑白名单配置)
    • 遍历操作远多于修改操作
    • 数据量较小(<1KB)
  • 不适合场景

    • 频繁写入
    • 实时性要求高
    • 大数据量(内存消耗大)

实践建议

  1. 事件监听器列表是典型应用场景
  2. 批量写入时使用addAll减少数组拷贝次数
  3. 考虑使用ConcurrentHashMap替代CopyOnWriteSet

3. Disruptor高性能队列原理

核心设计

图1

关键技术

  1. 环形数组:预分配内存,消除GC压力
  2. 序号栅栏:无锁的序列号管理
  3. 缓存行填充:避免伪共享

    // 缓存行填充示例
    class LhsPadding {
     protected long p1, p2, p3, p4, p5, p6, p7;
    }
    class Value extends LhsPadding {
     protected volatile long value;
    }
    class RhsPadding extends Value {
     protected long p9, p10, p11, p12, p13, p14, p15;
    }

实践建议

  1. 金融级交易系统优先考虑Disruptor
  2. 合理设置RingBuffer大小(2的幂次)
  3. 根据场景选择等待策略(BlockingWait/YieldingWait)

4. Guava的RateLimiter实现

令牌桶算法

// 使用示例
RateLimiter limiter = RateLimiter.create(5.0); // 每秒5个令牌
if (limiter.tryAcquire()) {
    doLimitedOperation();
}

// 预热模式
RateLimiter warmupLimiter = RateLimiter.create(5, 10, TimeUnit.SECONDS);

实现要点

  1. 平滑突发SmoothBursty实现
  2. 平滑预热SmoothWarmingUp实现
  3. 存储透支:允许未来令牌预支

实践建议

  1. API限流首选方案
  2. 突发流量场景使用tryAcquire非阻塞方式
  3. 分布式限流需结合Redis等中间件

5. 阻塞队列对比

ArrayBlockingQueue vs LinkedBlockingQueue

特性ArrayBlockingQueueLinkedBlockingQueue
底层结构数组链表
是否有界固定容量可选有界/无界
锁机制单锁(put/take共用)双锁(putLock/takeLock)
内存占用连续内存节点开销
GC影响较小较大
吞吐量中等(10w ops/s)高(50w ops/s)

实践建议

  1. 固定容量场景用ArrayBlockingQueue
  2. 高吞吐场景用LinkedBlockingQueue
  3. 无界队列要防止OOM,建议设置合理容量

最佳实践代码示例

// 生产者-消费者模式实现
BlockingQueue<Data> queue = new LinkedBlockingQueue<>(1000);

// 生产者
void produce(Data data) throws InterruptedException {
    if (!queue.offer(data, 1, TimeUnit.SECONDS)) {
        log.warn("Queue full, discard data");
    }
}

// 消费者
void consume() {
    while (running) {
        Data data = queue.poll(100, TimeUnit.MILLISECONDS);
        if (data != null) process(data);
    }
}

通过合理选择并发容器和工具库,可以显著提升Java应用的并发性能和稳定性。建议根据具体场景特点进行基准测试,选择最适合的并发组件。

添加新评论