Java 并发包(java.util.concurrent
,简称 JUC)是 JDK 1.5 引入的核心并发编程工具集,旨在解决多线程环境下的线程安全、资源同步、异步任务执行等问题。它提供了比传统 synchronized
更灵活、高效的并发控制机制,覆盖了锁、原子操作、并发集合、线程池、异步编程等核心场景。以下是 JUC 核心组件的深度解析:
一、JUC 核心设计思想
JUC 的设计围绕**分离“状态”与“同步逻辑”**展开,通过抽象类(如 AQS
)和接口(如 Lock
、Semaphore
)提供可扩展的并发原语。其核心目标是:
- 降低并发编程复杂度:通过封装底层同步机制(如操作系统信号量、互斥锁),提供更易用的 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
通过写时复制机制保证线程安全:写操作(如 add
、set
)时复制底层数组,读操作(如 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) Callable
与 FutureTask
FutureTask
是 RunnableFuture
的实现类(同时实现 Runnable
和 Future
),可将 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 最佳实践与注意事项
- 避免过度同步:优先使用无锁结构(如原子类、
ConcurrentHashMap
),减少锁竞争。 - 合理选择锁类型:读多写少用
ReadWriteLock
,需要乐观读用StampedLock
。 - 线程池参数调优:根据业务类型(CPU密集型/IO密集型)设置核心线程数,避免资源浪费。
- 处理中断异常:线程池中的任务需正确处理
InterruptedException
,避免线程无法终止。 - 避免 ABA 问题:使用
AtomicStampedReference
或AtomicMarkableReference
处理引用类型的原子更新。 - 监控与日志:通过
ThreadPoolExecutor
的监控方法(如getActiveCount()
)和日志记录线程池状态,及时发现性能瓶颈。
总结
JUC 是 Java 并发编程的核心工具集,覆盖了锁、原子操作、并发集合、线程池、异步编程等关键场景。熟练掌握 JUC 组件能显著提升多线程程序的性能和可靠性,但需根据具体场景选择合适的工具,并注意避免常见的并发陷阱(如竞态条件、死锁、ABA 问题)。随着 JDK 版本升级(如 JDK 9+ 的 Flow
API、VarHandles
),JUC 仍在不断演进,持续为高并发场景提供更强大的支持。