JUC并发包 速览

Java 并发包(java.util.concurrent,简称 JUC)是 JDK 1.5 引入的核心并发编程工具集,旨在解决多线程环境下的线程安全、资源同步、异步任务执行等问题。它提供了比传统 synchronized 更灵活、高效的并发控制机制,覆盖了锁、原子操作、并发集合、线程池、异步编程等核心场景。以下是 JUC 核心组件的深度解析:


一、JUC 核心设计思想

JUC 的设计围绕**分离“状态”与“同步逻辑”**展开,通过抽象类(如 AQS)和接口(如 LockSemaphore)提供可扩展的并发原语。其核心目标是:

  • 降低并发编程复杂度:通过封装底层同步机制(如操作系统信号量、互斥锁),提供更易用的 API。
  • 提升并发性能:通过 CAS(Compare-And-Swap)、无锁数据结构、分段锁等技术减少线程阻塞。
  • 增强灵活性:支持可中断锁、超时锁、公平/非公平锁等差异化需求。

二、核心组件详解

1. 锁机制:替代 synchronized 的更灵活方案

synchronized 是 JVM 内置的互斥锁,但功能有限(如不可中断、无超时机制)。JUC 提供了更强大的锁接口和实现类。

(1) Lock 接口与 ReentrantLock

Lock 是一个可扩展的锁接口,相比 synchronized 支持:

  • 可中断锁:通过 lockInterruptibly() 中断等待中的线程。
  • 超时锁:通过 tryLock(long, TimeUnit) 尝试获取锁,超时后返回。
  • 公平锁:通过构造函数 ReentrantLock(boolean fair) 指定是否按等待队列顺序分配锁。

ReentrantLock 示例

Lock lock = new ReentrantLock(true); // 公平锁

// 加锁
lock.lock();
try {
    // 临界区操作
} finally {
    lock.unlock(); // 必须释放锁
}

// 可中断加锁
try {
    lock.lockInterruptibly();
    // 临界区
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 恢复中断状态
}

// 超时加锁(避免死等)
if (lock.tryLock(3, TimeUnit.SECONDS)) {
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
} else {
    // 超时处理
}
(2) ReadWriteLock:读写分离锁

ReadWriteLock 定义了读锁(共享锁)和写锁(独占锁),适用于读多写少场景(如缓存)。
ReentrantReadWriteLock 是其实现类,支持:

  • 多个读线程同时获取读锁(共享锁)。
  • 写锁与读锁/写锁互斥(独占锁)。

示例

ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();

// 读操作(共享锁)
readLock.lock();
try {
    // 读取共享数据
} finally {
    readLock.unlock();
}

// 写操作(独占锁)
writeLock.lock();
try {
    // 修改共享数据
} finally {
    writeLock.unlock();
}
(3) StampedLock:乐观读锁(JDK 8+)

StampedLock 引入了乐观读机制,通过“戳(stamp)”标记数据版本,适用于读远多于写且数据不易变的场景。
乐观读不阻塞写线程,但需验证数据是否被修改(通过 validate(stamp))。

示例

StampedLock stampedLock = new StampedLock();

// 乐观读(无锁)
long stamp = stampedLock.tryOptimisticRead();
// 读取数据(假设data是共享变量)
int data = sharedData;
// 验证数据是否被修改(未被修改则有效)
if (!stampedLock.validate(stamp)) {
    // 数据已被修改,升级为读锁
    stamp = stampedLock.readLock();
    try {
        data = sharedData;
    } finally {
        stampedLock.unlockRead(stamp);
    }
}

// 写锁(独占)
long writeStamp = stampedLock.writeLock();
try {
    sharedData = newData;
} finally {
    stampedLock.unlockWrite(writeStamp);
}

2. 原子类:无锁的线程安全操作

原子类(java.util.concurrent.atomic)基于 CAS(Compare-And-Swap) 机制实现无锁的线程安全操作,避免了传统锁的开销(如上下文切换)。核心类包括:

(1) 基础原子类
  • AtomicInteger:原子整型操作(incrementAndGet()compareAndSet())。
  • AtomicLong:原子长整型操作。
  • AtomicReference<T>:原子引用类型操作(支持对象引用的原子更新)。

AtomicInteger 示例

AtomicInteger counter = new AtomicInteger(0);

// 原子递增(等价于 i++,但线程安全)
int newValue = counter.incrementAndGet(); 

// 原子比较并设置(CAS 核心)
boolean success = counter.compareAndSet(10, 20); // 若当前值为10,则设为20,返回true
(2) 数组原子类
  • AtomicIntegerArray:原子操作整数数组。
  • AtomicLongArray:原子操作长整型数组。
  • AtomicReferenceArray<T>:原子操作引用类型数组。

示例

AtomicIntegerArray arr = new AtomicIntegerArray(5); // 初始化长度为5的数组

// 原子修改索引2的值(初始为0)
arr.incrementAndGet(2); // 索引2的值变为1
(3) 引用原子类
  • AtomicMarkableReference<T>:原子引用 + 标记位(解决 ABA 问题)。
  • AtomicStampedReference<T>:原子引用 + 版本号(解决 ABA 问题)。

ABA 问题:线程1将变量从 A→B,线程2将 B→A,线程1误以为变量未变。
解决方案:通过 AtomicStampedReference 记录版本号,验证时检查版本是否一致。

示例

AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
int[] stampHolder = new int[1]; // 存储版本号

// 线程1:尝试修改 A→B
String oldVal = ref.get(stampHolder);
int oldStamp = stampHolder[0];
boolean success = ref.compareAndSet(oldVal, "B", oldStamp, oldStamp + 1);

// 线程2:尝试修改 B→A(假设此时版本号为1)
stampHolder[0] = ref.getStamp(); // 获取当前版本号(1)
success = ref.compareAndSet("B", "A", 1, 2); // 版本号匹配,修改成功

// 线程1再次检查:版本号已变,避免误判
stampHolder[0] = ref.getStamp();
String currentVal = ref.get(stampHolder); // 结果为"A",但版本号已更新

3. 并发集合:线程安全的容器

JUC 提供了一系列线程安全的集合类,解决了传统 Collections.synchronizedXXX() 性能差的问题(如全表锁)。核心类包括:

(1) ConcurrentHashMap:线程安全的哈希表

ConcurrentHashMap 采用**分段锁(JDK 7)CAS + synchronized 细粒度锁(JDK 8)**实现,支持高并发读写。
关键优化

  • JDK 7:将数据分成多个 Segment(段),每个段独立加锁(最多16段)。
  • JDK 8:取消分段锁,改用 CAS 更新头节点,冲突时使用 synchronized 锁定单个桶。

示例

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// 线程安全插入
map.put("key1", 100);

// 线程安全计算(原子操作)
map.merge("key1", 50, Integer::sum); // key1的值变为150

// 线程安全遍历(弱一致性,允许遍历时修改)
map.forEach((k, v) -> System.out.println(k + ": " + v));
(2) CopyOnWriteArrayList:写时复制列表

CopyOnWriteArrayList 通过写时复制机制保证线程安全:写操作(如 addset)时复制底层数组,读操作(如 get)直接访问原数组。
适用场景:读多写少(写操作代价高,适合缓存、配置列表)。

示例

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

// 写操作(复制数组)
list.add("A"); 
list.set(0, "B"); 

// 读操作(无锁)
for (String s : list) {
    System.out.println(s);
}
(3) BlockingQueue:阻塞队列

BlockingQueue 是线程安全的队列,支持阻塞插入/删除(当队列满/空时)。核心实现类:

  • ArrayBlockingQueue:有界队列(固定容量),基于数组。
  • LinkedBlockingQueue:可选有界队列(默认 Integer.MAX_VALUE),基于链表。
  • PriorityBlockingQueue:优先级队列(按元素自然顺序或自定义比较器排序)。
  • SynchronousQueue:无缓冲队列(插入操作必须等待另一个线程删除)。

生产者-消费者模型示例

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10); // 容量10

// 生产者线程
new Thread(() -> {
    try {
        for (int i = 0; i < 100; i++) {
            queue.put(i); // 队列满时阻塞
            System.out.println("生产:" + i);
        }
        queue.put(-1); // 发送终止信号
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}).start();

// 消费者线程
new Thread(() -> {
    try {
        while (true) {
            int num = queue.take(); // 队列空时阻塞
            if (num == -1) break;
            System.out.println("消费:" + num);
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}).start();

4. 线程池:管理线程的生命周期

线程池(java.util.concurrent.ThreadPoolExecutor)是管理线程的核心工具,避免频繁创建/销毁线程的开销。通过 Executors 工厂类可快速创建线程池,但更推荐直接使用 ThreadPoolExecutor 构造函数自定义参数。

(1) 核心参数
public ThreadPoolExecutor(
    int corePoolSize,       // 核心线程数(常驻线程)
    int maximumPoolSize,    // 最大线程数(核心+临时线程)
    long keepAliveTime,     // 临时线程空闲存活时间
    TimeUnit unit,          // 时间单位
    BlockingQueue<Runnable> workQueue,  // 任务队列(阻塞队列)
    ThreadFactory threadFactory,        // 线程工厂(自定义线程名等)
    RejectedExecutionHandler handler    // 拒绝策略(任务过多时的处理方式)
)
(2) 拒绝策略

当任务队列满且线程数达到 maximumPoolSize 时,触发拒绝策略:

  • AbortPolicy(默认):抛出 RejectedExecutionException
  • CallerRunsPolicy:由调用者线程执行任务(减缓任务提交速度)。
  • DiscardPolicy:静默丢弃新任务。
  • DiscardOldestPolicy:丢弃队列中最旧的任务,尝试重新提交当前任务。
(3) 使用示例
// 自定义线程池(推荐)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2,                  // 核心线程数
    5,                  // 最大线程数
    30, TimeUnit.SECONDS, // 空闲存活时间
    new LinkedBlockingQueue<>(10), // 任务队列(容量10)
    Executors.defaultThreadFactory(), // 默认线程工厂
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

// 提交任务
executor.submit(() -> {
    System.out.println("任务执行:" + Thread.currentThread().getName());
});

// 关闭线程池(不再接受新任务,已提交任务执行完毕)
executor.shutdown();
// 等待所有任务完成(超时可中断)
executor.awaitTermination(1, TimeUnit.MINUTES);
(4) 监控与调优
  • 线程池状态:通过 getPoolSize()(当前线程数)、getActiveCount()(活跃线程数)、getQueue().size()(队列任务数)监控。
  • 拒绝策略选择:根据业务场景选择(如关键任务用 CallerRunsPolicy,非关键任务用 DiscardPolicy)。
  • 合理配置参数:核心线程数根据业务类型(CPU密集型/IO密集型)调整(CPU密集型≈CPU核心数,IO密集型≈CPU核心数×2)。

5. Future 与 Callable:异步任务执行

Callable 是可返回结果的任务接口(类似 Runnable,但支持返回值和异常),Future 用于获取异步任务的结果。

(1) CallableFutureTask

FutureTaskRunnableFuture 的实现类(同时实现 RunnableFuture),可将 Callable 包装为任务提交给线程池。

示例

Callable<Integer> task = () -> {
    Thread.sleep(1000); // 模拟耗时操作
    return 42;
};

FutureTask<Integer> futureTask = new FutureTask<>(task);

// 提交任务到线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(futureTask);

// 获取结果(阻塞直到完成)
try {
    int result = futureTask.get(); // 阻塞1秒后返回42
    System.out.println("结果:" + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}
(2) CompletableFuture(JDK 8+)

CompletableFuture 支持异步任务的组合与编排(如链式调用、并行执行、异常处理),是更强大的异步编程工具。

核心方法

  • supplyAsync(Supplier):异步执行任务(有返回值)。
  • thenApply(Function):前一个任务完成后,对其结果进行处理(链式调用)。
  • thenAccept(Consumer):消费前一个任务的结果(无返回值)。
  • exceptionally(Function):处理异常。
  • allOf(CompletableFuture...):等待所有任务完成。
  • anyOf(CompletableFuture...):等待任意一个任务完成。

示例:异步任务链

CompletableFuture.supplyAsync(() -> {
    // 任务1:获取用户ID
    return 123;
}).thenApply(userId -> {
    // 任务2:根据用户ID查询订单
    return "订单_" + userId;
}).thenAccept(order -> {
    // 任务3:打印订单
    System.out.println("订单信息:" + order);
}).exceptionally(e -> {
    // 异常处理
    System.err.println("任务失败:" + e.getMessage());
    return null;
});

6. 并发工具类:协调线程执行

JUC 提供了 java.util.concurrent 包中的工具类,用于协调多个线程的执行顺序或状态。

(1) CountDownLatch:倒计时门闩

CountDownLatch 允许一个或多个线程等待其他线程完成操作。初始化时设置计数(count),每个线程完成任务后调用 countDown() 减少计数,主线程通过 await() 等待计数归零。

示例:主线程等待所有子线程完成

int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);

for (int i = 0; i < threadCount; i++) {
    new Thread(() -> {
        try {
            // 模拟任务执行
            Thread.sleep(1000);
            System.out.println("子线程完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            latch.countDown(); // 计数减1
        }
    }).start();
}

try {
    latch.await(); // 主线程等待计数归零(最多等待3秒)
    System.out.println("所有子线程完成,主线程继续");
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}
(2) CyclicBarrier:循环屏障

CyclicBarrier 允许一组线程互相等待,直到所有线程都到达屏障点(调用 await()),然后继续执行。屏障可重复使用(循环)。

示例:多线程协作计算

int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
    // 所有线程到达后执行(汇总结果)
    System.out.println("所有线程准备完毕,开始汇总");
});

for (int i = 0; i < threadCount; i++) {
    new Thread(() -> {
        try {
            System.out.println("线程" + Thread.currentThread().getId() + "到达屏障");
            barrier.await(); // 等待其他线程
            System.out.println("线程" + Thread.currentThread().getId() + "继续执行");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }).start();
}
(3) Semaphore:信号量

Semaphore 控制同时访问共享资源的线程数量(通过“许可”实现)。初始化时设置许可数量(permits),线程通过 acquire() 获取许可(无许可则阻塞),通过 release() 释放许可。

示例:限制并发连接数

int maxConnections = 5;
Semaphore semaphore = new Semaphore(maxConnections);

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire(); // 获取许可(最多5个线程同时执行)
            System.out.println("线程" + Thread.currentThread().getId() + "获取连接");
            Thread.sleep(2000); // 模拟使用连接
            semaphore.release(); // 释放许可
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
}
(4) Exchanger:交换数据

Exchanger 允许两个线程在某个同步点交换数据。线程通过 exchange(V data) 方法发送数据并等待对方响应。

示例:线程间交换结果

Exchanger<String> exchanger = new Exchanger<>();

// 线程A:发送数据A并接收数据B
new Thread(() -> {
    try {
        String dataA = "数据A";
        String dataB = exchanger.exchange(dataA); // 等待线程B交换
        System.out.println("线程A收到:" + dataB);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}).start();

// 线程B:发送数据B并接收数据A
new Thread(() -> {
    try {
        String dataB = "数据B";
        String dataA = exchanger.exchange(dataB); // 等待线程A交换
        System.out.println("线程B收到:" + dataA);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}).start();

三、JUC 最佳实践与注意事项

  1. 避免过度同步:优先使用无锁结构(如原子类、ConcurrentHashMap),减少锁竞争。
  2. 合理选择锁类型:读多写少用 ReadWriteLock,需要乐观读用 StampedLock
  3. 线程池参数调优:根据业务类型(CPU密集型/IO密集型)设置核心线程数,避免资源浪费。
  4. 处理中断异常:线程池中的任务需正确处理 InterruptedException,避免线程无法终止。
  5. 避免 ABA 问题:使用 AtomicStampedReferenceAtomicMarkableReference 处理引用类型的原子更新。
  6. 监控与日志:通过 ThreadPoolExecutor 的监控方法(如 getActiveCount())和日志记录线程池状态,及时发现性能瓶颈。

总结

JUC 是 Java 并发编程的核心工具集,覆盖了锁、原子操作、并发集合、线程池、异步编程等关键场景。熟练掌握 JUC 组件能显著提升多线程程序的性能和可靠性,但需根据具体场景选择合适的工具,并注意避免常见的并发陷阱(如竞态条件、死锁、ABA 问题)。随着 JDK 版本升级(如 JDK 9+ 的 Flow API、VarHandles),JUC 仍在不断演进,持续为高并发场景提供更强大的支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值