Java核心基础八:JUC线程安全容器和原子类

JUC包详解


一、什么是JUC包?

  • JUC 全称为 Java Util Concurrent,是Java标准库中专门用于支持高并发编程的工具包,提供了一系列线程安全的类、锁机制、原子操作、并发容器和线程池等工具。

  • 目标:简化多线程编程,提高并发性能,避免开发者直接操作底层线程和锁的复杂性。


二、JUC包的作用

  1. 提高并发性能:通过无锁算法(CAS)、分段锁、线程池等技术优化资源利用。

  2. 简化线程管理:提供线程池(ExecutorService)、并发工具类(如CountDownLatch)等,减少手动管理线程的开销。

  3. 保证线程安全:提供线程安全的数据结构(如ConcurrentHashMapCopyOnWriteArrayList)和原子类(如AtomicInteger)。

  4. 解决复杂并发问题:支持分布式锁、条件变量、信号量等高级同步机制。


JUC包核心类详解


(一)线程安全容器

一、ConcurrentHashMap

作用:线程安全的哈希表,支持高并发读写操作。
适用场景:高并发环境下的键值存储(如缓存、计数器)。

1. 实现原理
  • JDK7分段锁(Segment)
    将整个哈希表分成多个段(Segment),每个段独立加锁,不同段的操作可并行执行。

    • 默认16个段,并发度由段数决定。

    • 段内结构:数组 + 链表。

  • JDK8+CAS + synchronized锁单个桶(Node)

    • 数组 + 链表/红黑树(链表长度≥8时转红黑树)。

    • 锁粒度更细,仅锁住冲突的哈希桶(Node头节点)。

    • 使用CAS尝试无锁插入,失败后通过synchronized锁住头节点。

2. 源码关键点(JDK8)
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable(); // 初始化数组
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // CAS尝试无锁插入新节点
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;
        } else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f); // 协助扩容
        else {
            synchronized (f) { // 锁住头节点
                // 处理链表或红黑树插入
            }
        }
    }
    return null;
}
3. 对比Hashtable
特性ConcurrentHashMapHashtable
锁粒度分段锁或桶锁(高并发)全局锁(性能差)
Null支持不允许Key/Value为null允许null(但易引发歧义)
扩容机制分段扩容(减少阻塞)全表锁扩容
并发度

面试重点

  • 为什么ConcurrentHashMap不允许null值?
    避免歧义(如map.get(key)返回null时,无法区分是key不存在还是value为null)。


二、CopyOnWriteArrayList

作用:线程安全的动态数组,读操作无锁,写操作通过复制数组实现。
适用场景:读多写少(如监听器列表、配置管理)。

1. 实现原理
  • 写时复制(Copy-On-Write)
    每次修改操作(add、set、remove)时,复制原数组生成新数组,修改完成后替换原数组。

    • 读操作直接访问原数组,无需加锁。

    • 写操作加锁(ReentrantLock),保证线程安全。

2. 源码关键点
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();
    }
}
3. 优缺点
  • 优点

    • 读操作完全无锁,性能极高。

    • 适合读多写少的场景。

  • 缺点

    • 写操作需要复制数组,内存占用大。

    • 数据一致性弱(读操作可能读到旧数据)。

4. 对比Vector
特性CopyOnWriteArrayListVector
锁机制写时加锁,读无锁所有方法加锁(性能差)
内存开销写操作复制数组,开销大无额外内存开销
适用场景读多写少写操作频繁

面试重点

  • 为什么CopyOnWriteArrayList的迭代器不会抛出ConcurrentModificationException
    迭代器遍历的是原数组的快照,写操作不影响迭代过程。


(二)原子类

三、Atomic类(如AtomicInteger)

作用:通过CAS(Compare And Swap)实现无锁原子操作。
适用场景:计数器、状态标志等需要原子更新的场景。

1. 核心原理
  • CAS操作

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
  • 比较当前值与期望值,若相等则更新为新值,否则重试。

  • 硬件级指令(如x86的cmpxchg)保证原子性。

2. 解决ABA问题
  • AtomicStampedReference:通过版本号(Stamp)避免ABA问题。

AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(100, 0);
atomicRef.compareAndSet(100, 101, 0, 1); // 版本号从0→1
3. 常见Atomic类
类名作用
AtomicInteger原子更新整型值
AtomicLong原子更新长整型值
AtomicReference原子更新对象引用
AtomicIntegerArray原子更新整型数组中的元素
4. 示例代码
AtomicInteger counter = new AtomicInteger(0);
// 多线程安全自增
counter.incrementAndGet(); // 输出:1

面试重点

  • CAS的缺点是什么?

    • ABA问题(通过版本号解决)。

    • 自旋时间长时CPU开销大(如高并发场景)。


总结与对比

类名核心机制适用场景性能特点
ConcurrentHashMapCAS + 分段锁/桶锁高并发读写高吞吐量,低锁竞争
CopyOnWriteArrayList写时复制 + ReentrantLock读多写少读无锁,写开销大
Atomic类CAS无锁操作简单原子操作(如计数器)无锁,高并发下性能优异

面试高频问题

  1. ConcurrentHashMap在JDK7和JDK8中的实现区别?
    JDK7使用分段锁,JDK8改用CAS+桶锁,减少锁粒度。

  2. CopyOnWriteArrayList的写操作为什么加锁?
    防止多个线程同时修改导致数据不一致。

  3. Atomic类能否保证多个操作的原子性?
    单个CAS操作是原子的,但多个操作需结合AtomicReference或锁。

  4. 如何选择ConcurrentHashMap和Hashtable?
    高并发场景优先选择ConcurrentHashMap。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值