Java JUC常用工具类详解(附适用场景+简单示例)
在Java并发编程中,JUC(java.util.concurrent)包是核心工具集,它弥补了传统synchronized关键字和Thread类的局限性,提供了更高效、灵活的并发解决方案,涵盖原子操作、锁机制、线程协作、并发容器等核心能力,是处理高并发场景的必备工具。
本文将聚焦JUC中最常用的工具类,拆解其核心功能、适用场景,并搭配极简示例,帮助开发者快速掌握,避免在并发场景中踩坑。(适合Java初级/中级开发者,面试+实际开发均适用)
一、JUC核心基础认知
JUC从Java 5开始引入,核心设计思路包括:分离锁与同步机制、基于CPU原语的CAS无锁优化、线程池化管理、并发容器优化等,其底层大多依赖AQS(AbstractQueuedSynchronizer)同步器——通过状态变量state和双向阻塞队列,实现线程的排队与资源分配,是ReentrantLock、CountDownLatch等工具的底层骨架。
下面重点讲解开发中高频使用的工具类,按“工具类→核心功能→适用场景→极简示例”的结构展开,兼顾实用性和易懂性。
二、原子操作类(java.util.concurrent.atomic)
核心特点:基于CAS(Compare-And-Swap)实现无锁原子操作,避免synchronized的阻塞开销,保证共享变量的线程安全,无需手动加锁解锁,性能更优。
1. AtomicInteger / AtomicLong / AtomicBoolean
核心功能:分别实现int、long、boolean类型的原子更新,提供自增、自减、比较并交换(CAS)等方法。
适用场景:高并发环境下的计数器、状态标记,无需复杂同步逻辑的场景。
-
AtomicInteger:接口调用次数统计、订单编号自增、简单计数器;
-
AtomicLong:分布式ID生成(结合时间戳)、大数值累加;
-
AtomicBoolean:线程安全的开关状态(如服务启停标记、任务是否完成)。
极简示例(AtomicInteger/AtomicLong/AtomicBoolean):
// 完整示例:三种原子类的实际使用场景
public class AtomicDemo {
// 1. AtomicInteger:接口调用次数统计
private static final AtomicInteger interfaceCount = new AtomicInteger(0);
// 2. AtomicLong:分布式ID生成(简化版)
private static final AtomicLong distributedId = new AtomicLong(System.currentTimeMillis());
// 3. AtomicBoolean:服务启停标记
private static final AtomicBoolean serviceRunning = new AtomicBoolean(true);
public static void main(String[] args) throws InterruptedException {
// 模拟10个线程并发调用接口,统计调用次数
for (int i = 0; i < 10; i++) {
new Thread(() -> {
if (serviceRunning.get()) { // 检查服务是否运行
interfaceCount.incrementAndGet(); // 原子自增
System.out.println("接口调用成功,当前调用次数:" + interfaceCount.get());
}
}).start();
}
// 模拟生成3个分布式ID
for (int i = 0; i < 3; i++) {
System.out.println("生成分布式ID:" + distributedId.incrementAndGet());
}
// 停止服务
serviceRunning.set(false);
// 再次调用接口,验证服务已停止
new Thread(() -> {
if (serviceRunning.get()) {
interfaceCount.incrementAndGet();
System.out.println("接口调用成功");
} else {
System.out.println("服务已停止,拒绝调用");
}
}).start();
Thread.sleep(1000);
System.out.println("最终接口调用次数:" + interfaceCount.get());
}
}
2. AtomicStampedReference
核心功能:解决CAS操作的ABA问题(共享变量被反复修改后,表面值不变导致的误判),通过“值+版本号”的组合,保证原子更新的准确性。
核心功能:解决CAS操作的ABA问题(共享变量被反复修改后,表面值不变导致的误判),通过“值+版本号”的组合,保证原子更新的准确性。
适用场景:并发环境下,共享变量可能被多次修改,需要避免ABA误判的场景(如链表节点修改、缓存更新)。
完整示例:
// 解决ABA问题示例:模拟缓存更新场景
public class AtomicStampedReferenceDemo {
// 初始化:值为"缓存数据1",版本号为1
private static final AtomicStampedReference<String> cache = new AtomicStampedReference<>("缓存数据1", 1);
public static void main(String[] args) {
// 线程1:读取缓存,模拟业务处理后更新缓存(正常更新)
new Thread(() -> {
int stamp = cache.getStamp(); // 获取当前版本号
String currentCache = cache.getReference(); // 获取当前值
System.out.println("线程1:当前缓存=" + currentCache + ",版本号=" + stamp);
// 模拟业务处理,500ms后更新缓存
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 只有版本号匹配(还是1),才更新缓存,避免ABA问题
boolean updateSuccess = cache.compareAndSet(
currentCache,
"缓存数据2",
stamp,
stamp + 1
);
System.out.println("线程1:缓存更新" + (updateSuccess ? "成功" : "失败") + ",新版本号=" + cache.getStamp());
}).start();
// 线程2:模拟中间修改(ABA场景)
new Thread(() -> {
try {
Thread.sleep(200); // 等待线程1读取缓存后,修改值
} catch (InterruptedException e) {
e.printStackTrace();
}
int stamp = cache.getStamp();
// 第一次修改:将缓存改为"临时数据",版本号+1(变为2)
cache.compareAndSet(cache.getReference(), "临时数据", stamp, stamp + 1);
System.out.println("线程2:第一次修改缓存为临时数据,版本号=" + cache.getStamp());
// 第二次修改:将缓存改回原值"缓存数据1",版本号+1(变为3)
stamp = cache.getStamp();
cache.compareAndSet(cache.getReference(), "缓存数据1", stamp, stamp + 1);
System.out.println("线程2:第二次修改缓存回原值,版本号=" + cache.getStamp());
}).start();
// 最终线程1更新会失败,因为版本号已从1变为3,避免ABA误判
}
}
3. LongAdder
核心功能:高并发场景下的累加器,优化AtomicLong的性能——AtomicLong在高并发时会因CAS竞争频繁导致自旋开销,LongAdder通过分段累加,最后合并结果,提升并发效率。
核心功能:高并发场景下的累加器,优化AtomicLong的性能——AtomicLong在高并发时会因CAS竞争频繁导致自旋开销,LongAdder通过分段累加,最后合并结果,提升并发效率。
适用场景:海量并发计数(如秒杀活动参与人数、系统QPS统计)。
完整示例(对比AtomicLong):
// 对比LongAdder和AtomicLong在高并发下的性能差异
public class LongAdderDemo {
private static final LongAdder longAdder = new LongAdder();
private static final AtomicLong atomicLong = new AtomicLong(0);
private static final int THREAD_NUM = 100; // 100个并发线程
private static final int COUNT_PER_THREAD = 100000; // 每个线程累加10万次
public static void main(String[] args) throws InterruptedException {
// 测试LongAdder性能
long start1 = System.currentTimeMillis();
Thread[] threads1 = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++) {
threads1[i] = new Thread(() -> {
for (int j = 0; j < COUNT_PER_THREAD; j++) {
longAdder.increment(); // 累加
}
});
threads1[i].start();
}
for (Thread t : threads1) {
t.join();
}
long end1 = System.currentTimeMillis();
System.out.println("LongAdder累加结果:" + longAdder.sum());
System.out.println("LongAdder耗时:" + (end1 - start1) + "ms");
// 测试AtomicLong性能
long start2 = System.currentTimeMillis();
Thread[] threads2 = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++) {
threads2[i] = new Thread(() -> {
for (int j = 0; j < COUNT_PER_THREAD; j++) {
atomicLong.incrementAndGet(); // 累加
}
});
threads2[i].start();
}
for (Thread t : threads2) {
t.join();
}
long end2 = System.currentTimeMillis();
System.out.println("AtomicLong累加结果:" + atomicLong.get());
System.out.println("AtomicLong耗时:" + (end2 - start2) + "ms");
// 结论:高并发下,LongAdder耗时远低于AtomicLong
}
}
三、锁机制工具类(java.util.concurrent.locks)
核心特点:替代synchronized关键字,提供更细粒度的锁控制(可中断、超时获取、公平锁/非公平锁),支持多条件队列,灵活性更高,底层依赖AQS实现。
1. ReentrantLock(可重入锁)
核心功能:可重入(同一线程可多次获取锁)、支持公平锁和非公平锁(默认非公平锁,性能更优),提供tryLock()(非阻塞获取锁)、lockInterruptibly()(可中断获取锁)等方法,需手动释放锁(建议放在finally块中)。
适用场景:需要灵活控制锁的场景(如超时释放、中断获取锁)、高并发读写场景(配合Condition使用)。
完整示例(含公平锁、超时获取、可中断):
// ReentrantLock完整用法:公平锁、超时获取、可中断
public class ReentrantLockDemo {
// 1. 公平锁(传入true),保证线程按请求顺序获取锁
private static final ReentrantLock fairLock = new ReentrantLock(true);
// 2. 非公平锁(默认),性能更优
private static final ReentrantLock nonFairLock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 测试1:公平锁的顺序性
System.out.println("=== 测试公平锁 ===");
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
fairLock.lock();
try {
System.out.println("公平锁-线程" + finalI + "获取锁,执行业务");
Thread.sleep(100); // 模拟业务耗时
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
fairLock.unlock();
System.out.println("公平锁-线程" + finalI + "释放锁");
}
}, "Thread-" + i).start();
}
Thread.sleep(500); // 等待公平锁测试完成
// 测试2:非公平锁(无顺序)
System.out.println("\n=== 测试非公平锁 ===");
for (int i = 0; i < 3; i++) {
int finalI = i;
new Thread(() -> {
nonFairLock.lock();
try {
System.out.println("非公平锁-线程" + finalI + "获取锁,执行业务");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
nonFairLock.unlock();
System.out.println("非公平锁-线程" + finalI + "释放锁");
}
}, "Thread-" + i).start();
}
Thread.sleep(500);
// 测试3:超时获取锁(避免死锁)
System.out.println("\n=== 测试超时获取锁 ===");
Thread timeoutThread = new Thread(() -> {
try {
// 尝试获取锁,最多等待1秒,获取失败则放弃
boolean locked = nonFairLock.tryLock(1, TimeUnit.SECONDS);
if (locked) {
System.out.println("超时测试线程:获取锁成功,执行业务");
} else {
System.out.println("超时测试线程:获取锁失败,放弃执行");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 只有持有锁时,才释放锁
if (nonFairLock.isHeldByCurrentThread()) {
nonFairLock.unlock();
}
}
});
timeoutThread.start();
// 主线程先获取锁,让超时线程等待
nonFairLock.lock();
Thread.sleep(1500);
nonFairLock.unlock();
}
}
2. ReentrantReadWriteLock(读写锁)
核心功能:分离读操作和写操作,实现“读共享、写独占”——多个线程可同时读取,同一时刻只有一个线程能写入,大幅提升读多写少场景的性能。
核心功能:分离读操作和写操作,实现“读共享、写独占”——多个线程可同时读取,同一时刻只有一个线程能写入,大幅提升读多写少场景的性能。
适用场景:读多写少的高并发场景(如缓存查询、配置读取、商品详情页渲染)。
完整示例(读多写少场景):
// ReentrantReadWriteLock示例:模拟商品详情页(读多写少)
public class ReentrantReadWriteLockDemo {
// 读写锁实例
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读锁
private static final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
// 写锁
private static final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// 模拟商品数据(共享资源)
private static Map<String, String> productInfo = new HashMap<>();
static {
// 初始化商品数据
productInfo.put("product1", "手机:1999元");
productInfo.put("product2", "电脑:5999元");
}
// 读操作:查询商品详情(多线程可同时读)
public static String getProductInfo(String productId) {
readLock.lock(); // 获取读锁
try {
System.out.println(Thread.currentThread().getName() + ":查询商品" + productId);
Thread.sleep(50); // 模拟查询耗时
return productInfo.getOrDefault(productId, "商品不存在");
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} finally {
readLock.unlock(); // 释放读锁
}
}
// 写操作:更新商品价格(同一时刻只能一个线程写)
public static void updateProductPrice(String productId, String newPrice) {
writeLock.lock(); // 获取写锁
try {
System.out.println("\n" + Thread.currentThread().getName() + ":更新商品" + productId + "价格");
Thread.sleep(200); // 模拟更新耗时
productInfo.put(productId, newPrice);
System.out.println(Thread.currentThread().getName() + ":更新完成,新价格:" + newPrice + "\n");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock(); // 释放写锁
}
}
public static void main(String[] args) {
// 模拟10个读线程(并发查询)
for (int i = 0; i < 10; i++) {
new Thread(() -> {
String result = getProductInfo("product1");
System.out.println(Thread.currentThread().getName() + ":查询结果:" + result);
}, "读线程-" + i).start();
}
// 模拟2个写线程(更新商品价格)
new Thread(() -> updateProductPrice("product1", "手机:1899元"), "写线程-1").start();
new Thread(() -> updateProductPrice("product2", "电脑:5799元"), "写线程-2").start();
}
}
3. StampedLock(乐观读锁)
核心功能:优化ReentrantReadWriteLock的性能,支持乐观读模式——读操作无需加锁,仅在写入时检查版本号,避免读操作的阻塞,适合读极多、写极少的场景。
核心功能:优化ReentrantReadWriteLock的性能,支持乐观读模式——读操作无需加锁,仅在写入时检查版本号,避免读操作的阻塞,适合读极多、写极少的场景。
适用场景:高频读、低频写的场景(如日志查询、报表统计)。
完整示例(乐观读模式):
// StampedLock示例:乐观读模式(读极多写极少)
public class StampedLockDemo {
// StampedLock实例
private static final StampedLock stampedLock = new StampedLock();
// 共享资源:模拟日志数据
private static List<String> logList = new ArrayList<>();
static {
// 初始化日志数据
logList.add("2024-05-01 10:00:00 系统启动");
logList.add("2024-05-01 10:05:00 用户登录");
}
// 乐观读:读极多场景,无需加锁,效率最高
public static String queryLog(int index) {
// 获取乐观读戳记(无锁)
long stamp = stampedLock.tryOptimisticRead();
// 读取共享资源
String log = null;
if (index >= 0 && index < logList.size()) {
log = logList.get(index);
}
// 验证戳记:如果期间没有写操作,戳记不变,读取有效
if (stampedLock.validate(stamp)) {
System.out.println(Thread.currentThread().getName() + ":乐观读成功,日志:" + log);
return log;
}
// 验证失败(期间有写操作),升级为悲观读锁,重新读取
stamp = stampedLock.readLock();
try {
System.out.println(Thread.currentThread().getName() + ":乐观读失败,升级为悲观读");
if (index >= 0 && index < logList.size()) {
log = logList.get(index);
}
return log;
} finally {
stampedLock.unlockRead(stamp); // 释放读锁
}
}
// 写操作:低频写,独占锁
public static void addLog(String log) {
// 获取写锁,返回戳记
long stamp = stampedLock.writeLock();
try {
System.out.println("\n" + Thread.currentThread().getName() + ":添加日志:" + log);
Thread.sleep(100); // 模拟写操作耗时
logList.add(log);
System.out.println(Thread.currentThread().getName() + ":日志添加完成\n");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
stampedLock.unlockWrite(stamp); // 释放写锁
}
}
public static void main(String[] args) {
// 模拟20个读线程(高频读)
for (int i = 0; i < 20; i++) {
int finalI = i % logList.size();
new Thread(() -> queryLog(finalI), "读线程-" + i).start();
}
// 模拟1个写线程(低频写)
new Thread(() -> addLog("2024-05-01 10:10:00 数据查询"), "写线程-1").start();
}
}
四、线程协作工具类(核心高频)
这类工具类用于实现多线程间的协同工作,解决“线程等待”“并发控制”等问题,底层均基于AQS实现,是开发中处理多线程协作的常用工具。
1. CountDownLatch(倒计时门闩)
核心功能:维护一个递减计数器,主线程调用await()方法阻塞,直到所有子线程执行完并调用countDown()将计数器减至0,主线程才继续执行——一次性使用,计数器归零后无法重置。
适用场景:主线程等待多个子线程全部完成后再执行的场景。
-
服务启动:网关启动时,等待数据库、缓存、消息队列等依赖服务全部就绪;
-
批量任务:主线程等待所有子线程完成批量数据处理后,汇总结果;
-
游戏加载:等待所有玩家加载完成后,启动游戏。
完整示例(服务启动场景,含异常处理):
// CountDownLatch完整示例:服务启动,处理子线程异常
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 计数器:3个依赖服务(数据库、缓存、消息队列)
CountDownLatch latch = new CountDownLatch(3);
// 1. 启动数据库服务(模拟异常场景)
new Thread(() -> {
try {
System.out.println("数据库服务:开始启动...");
// 模拟数据库连接失败(异常)
int errorCode = 1; // 0=成功,1=失败
if (errorCode == 1) {
throw new RuntimeException("数据库连接超时,启动失败");
}
System.out.println("数据库服务:启动完成");
} catch (Exception e) {
System.out.println("数据库服务:启动异常:" + e.getMessage());
} finally {
// 无论成功失败,都要countDown(),避免主线程永久阻塞
latch.countDown();
System.out.println("数据库服务:计数器减1,剩余:" + latch.getCount());
}
}, "数据库线程").start();
// 2. 启动缓存服务(正常启动)
new Thread(() -> {
try {
System.out.println("缓存服务:开始启动...");
Thread.sleep(800); // 模拟启动耗时
System.out.println("缓存服务:启动完成");
} catch (InterruptedException e) {
System.out.println("缓存服务:启动异常:" + e.getMessage());
} finally {
latch.countDown();
System.out.println("缓存服务:计数器减1,剩余:" + latch.getCount());
}
}, "缓存线程").start();
// 3. 启动消息队列服务(正常启动)
new Thread(() -> {
try {
System.out.println("消息队列服务:开始启动...");
Thread.sleep(1000); // 模拟启动耗时
System.out.println("消息队列服务:启动完成");
} catch (InterruptedException e) {
System.out.println("消息队列服务:启动异常:" + e.getMessage());
} finally {
latch.countDown();
System.out.println("消息队列服务:计数器减1,剩余:" + latch.getCount());
}
}, "消息队列线程").start();
// 主线程阻塞,等待所有依赖服务启动(最多等待3秒,避免无限阻塞)
boolean allReady = latch.await(3, TimeUnit.SECONDS);
if (allReady) {
System.out.println("\n所有依赖服务就绪,网关开始接收请求");
} else {
System.out.println("\n部分服务启动超时,网关启动失败,请检查依赖服务");
}
}
}
注意:countDown()必须放在finally块中,避免子线程异常退出导致计数器无法归零,主线程永久阻塞。
2. CyclicBarrier(循环屏障)
核心功能:维护一个递增计数器,多个线程调用await()方法后阻塞,直到所有线程都到达“屏障点”,计数器达到预设值,所有阻塞线程同时继续执行——可通过reset()方法重置计数器,循环使用。
适用场景:多个线程协同完成一个分阶段任务,需所有线程完成当前阶段后,再进入下一阶段。
-
分阶段计算:金融风控中,多个线程分别计算不同维度的数据,所有线程计算完成后,再汇总结果;
-
赛车比赛:所有选手就位后,同时开始比赛;
-
批量数据处理:分批次处理数据,每批次所有线程完成后,再处理下一批。
适用场景:多个线程协同完成一个分阶段任务,需所有线程完成当前阶段后,再进入下一阶段。
-
分阶段计算:金融风控中,多个线程分别计算不同维度的数据,所有线程计算完成后,再汇总结果;
-
赛车比赛:所有选手就位后,同时开始比赛;
-
批量数据处理:分批次处理数据,每批次所有线程完成后,再处理下一批。
完整示例(分阶段计算,循环使用):
// CyclicBarrier完整示例:分阶段计算,支持循环使用
public class CyclicBarrierDemo {
// 3个线程协同,达到屏障后执行汇总任务(第二个参数为屏障触发后的回调)
private static final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("\n=== 所有线程完成当前阶段,开始汇总 ===");
// 模拟汇总逻辑
System.out.println("汇总完成,进入下一阶段\n");
});
public static void main(String[] args) {
// 3个线程,协同完成2个阶段的计算任务
for (int i = 0; i < 3; i++) {
int threadId = i;
new Thread(() -> {
try {
// 第一阶段计算
System.out.println("线程" + threadId + ":开始第一阶段计算");
Thread.sleep((threadId + 1) * 500); // 模拟不同耗时
System.out.println("线程" + threadId + ":第一阶段计算完成,等待其他线程");
barrier.await(); // 到达屏障,等待其他线程
// 第二阶段计算(循环使用屏障)
System.out.println("线程" + threadId + ":开始第二阶段计算");
Thread.sleep((threadId + 1) * 300);
System.out.println("线程" + threadId + ":第二阶段计算完成,等待其他线程");
barrier.await(); // 再次到达屏障
System.out.println("线程" + threadId + ":所有阶段完成,退出");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
关键区别:CountDownLatch是“主线程等子线程”(非对等协作),CyclicBarrier是“所有线程互相等”(对等协作),且可循环使用。
3. Semaphore(信号量)
核心功能:维护一组“许可”,线程需调用acquire()获取许可才能执行,执行完成后调用release()归还许可,通过控制许可数量,限制同时执行的线程数,实现限流。
适用场景:控制并发访问数量,保护有限资源。
-
接口限流:限制同时访问某个接口的线程数,避免服务过载;
-
资源保护:控制同时访问数据库连接、文件句柄等有限资源的线程数;
-
停车场模拟:控制同时停放的车辆数(许可数=停车位数量)。
完整示例(接口限流+资源保护,含超时获取):
// Semaphore完整示例:接口限流+数据库连接池保护
public class SemaphoreDemo {
// 1. 接口限流:最多5个线程同时访问接口
private static final Semaphore apiSemaphore = new Semaphore(5);
// 2. 数据库连接池保护:最多3个线程同时获取连接
private static final Semaphore dbSemaphore = new Semaphore(3);
// 模拟数据库连接
private static class DBConnection {
private String id;
public DBConnection(String id) {
this.id = id;
}
public void close() {
System.out.println("数据库连接" + id + ":关闭");
}
}
// 模拟数据库连接获取(受Semaphore保护)
private static DBConnection getDBConnection() throws InterruptedException {
// 超时获取连接,最多等待2秒
boolean acquired = dbSemaphore.tryAcquire(2, TimeUnit.SECONDS);
if (!acquired) {
throw new RuntimeException("获取数据库连接超时");
}
// 模拟创建连接
DBConnection conn = new DBConnection(String.valueOf(System.currentTimeMillis()).substring(8));
System.out.println("数据库连接" + conn.id + ":获取成功");
return conn;
}
// 模拟接口请求(受Semaphore限流)
private static void handleRequest(int requestId) {
try {
// 获取接口访问许可
apiSemaphore.acquire();
System.out.println("请求" + requestId + ":获取接口许可,开始处理");
// 业务逻辑:查询数据库(需要获取数据库连接)
DBConnection conn = getDBConnection();
try {
// 模拟数据库查询耗时
Thread.sleep(500);
System.out.println("请求" + requestId + ":数据库查询完成");
} finally {
conn.close();
dbSemaphore.release(); // 归还数据库连接许可
}
System.out.println("请求" + requestId + ":处理完成");
} catch (InterruptedException | RuntimeException e) {
System.out.println("请求" + requestId + ":处理失败:" + e.getMessage());
} finally {
apiSemaphore.release(); // 归还接口访问许可
}
}
public static void main(String[] args) {
// 模拟15个并发请求(接口限流5个,数据库连接限流3个)
for (int i = 1; i <= 15; i++) {
int finalI = i;
new Thread(() -> handleRequest(finalI), "请求线程-" + i).start();
}
}
}
注意:release()必须放在finally块中,避免许可泄漏,导致可用许可逐渐减少,最终系统阻塞。
4. Exchanger(数据交换器)
核心功能:仅支持两个线程之间交换单个数据对象,第一个线程到达后阻塞等待,第二个线程到达后完成数据交换,双方同时继续执行。
适用场景:场景较狭窄,主要用于两个线程的协作数据交换。
-
生产者-消费者:一个线程填充数据缓冲区,另一个线程处理缓冲区,处理完后交换空缓冲区;
-
遗传算法:两个线程各自持有部分数据,交换子集进行交叉运算。
适用场景:场景较狭窄,主要用于两个线程的协作数据交换。
-
生产者-消费者:一个线程填充数据缓冲区,另一个线程处理缓冲区,处理完后交换空缓冲区;
-
遗传算法:两个线程各自持有部分数据,交换子集进行交叉运算。
完整示例(生产者-消费者缓冲区交换):
// Exchanger完整示例:生产者-消费者缓冲区交换
public class ExchangerDemo {
// 数据交换器:交换String类型的缓冲区(也可交换自定义对象)
private static final Exchanger<StringBuffer> exchanger = new Exchanger<>();
// 生产者缓冲区(填充数据)
private static final StringBuffer producerBuffer = new StringBuffer();
// 消费者缓冲区(处理数据后清空)
private static final StringBuffer consumerBuffer = new StringBuffer();
// 生产者线程:填充缓冲区,然后与消费者交换
private static class Producer implements Runnable {
@Override
public void run() {
try {
for (int i = 1; i <= 3; i++) {
// 清空缓冲区,填充新数据
producerBuffer.setLength(0);
producerBuffer.append("批量数据").append(i).append(":").append("a,b,c,d,e");
System.out.println("生产者:填充数据完成,缓冲区内容:" + producerBuffer);
// 交换缓冲区:将填充好的缓冲区交给消费者,获取消费者的空缓冲区
exchanger.exchange(producerBuffer, 1, TimeUnit.SECONDS);
System.out.println("生产者:交换完成,获取空缓冲区\n");
Thread.sleep(1000); // 模拟生产间隔
}
} catch (InterruptedException | TimeoutException e) {
System.out.println("生产者:交换超时或被中断");
}
}
}
// 消费者线程:处理缓冲区数据,然后与生产者交换
private static class Consumer implements Runnable {
@Override
public void run() {
try {
for (int i = 1; i <= 3; i++) {
// 交换缓冲区:将空缓冲区交给生产者,获取生产者的填充好的缓冲区
StringBuffer filledBuffer = exchanger.exchange(consumerBuffer, 1, TimeUnit.SECONDS);
System.out.println("消费者:获取填充缓冲区,内容:" + filledBuffer);
// 处理数据(模拟)
String[] data = filledBuffer.toString().split(":")[1].split(",");
System.out.println("消费者:处理数据,共" + data.length + "条,处理完成");
// 清空缓冲区,准备下次交换
filledBuffer.setLength(0);
System.out.println("消费者:缓冲区清空,准备交换\n");
Thread.sleep(1500); // 模拟消费间隔
}
} catch (InterruptedException | TimeoutException e) {
System.out.println("消费者:交换超时或被中断");
}
}
}
public static void main(String[] args) {
new Thread(new Producer(), "生产者线程").start();
new Thread(new Consumer(), "消费者线程").start();
}
}
注意:生产环境建议使用带超时的exchange()方法,避免线程无限期阻塞。
五、并发容器类(线程安全集合)
核心特点:替代线程不安全的ArrayList、HashMap等集合,无需手动加锁,底层通过CAS或锁机制保证线程安全,提升并发场景下的性能。
1. ConcurrentHashMap
核心功能:线程安全的HashMap,支持高并发读写,Java 8后采用“CAS+ synchronized”替代分段锁,进一步提升性能,支持高效的并发更新和查询。
核心功能:线程安全的HashMap,支持高并发读写,Java 8后采用“CAS+ synchronized”替代分段锁,进一步提升性能,支持高效的并发更新和查询。
适用场景:高并发环境下的键值对存储(如缓存、会话存储),替代线程不安全的HashMap+Collections.synchronizedMap()。
完整示例(缓存存储场景):
// ConcurrentHashMap完整示例:高并发缓存存储
public class ConcurrentHashMapDemo {
// 线程安全的缓存,初始容量16,负载因子0.75
private static final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>(16, 0.75f);
// 缓存查询(不存在则加载)
public static Object getCache(String key) {
// 1. 先查询缓存
Object value = cache.get(key);
if (value != null) {
System.out.println(Thread.currentThread().getName() + ":缓存命中,key=" + key + ",value=" + value);
return value;
}
// 2. 缓存未命中,加载数据(双重检查,避免重复加载)
synchronized (cache) {
value = cache.get(key);
if (value == null) {
System.out.println(Thread.currentThread().getName() + ":缓存未命中,加载数据,key=" + key);
// 模拟从数据库加载数据
value = "加载的数据-" + key;
cache.put(key, value); // 存入缓存
System.out.println(Thread.currentThread().getName() + ":数据加载完成,存入缓存");
}
}
return value;
}
// 缓存更新
public static void updateCache(String key, Object value) {
cache.put(key, value);
System.out.println(Thread.currentThread().getName() + ":缓存更新,key=" + key + ",新value=" + value);
}
// 缓存删除
public static void removeCache(String key) {
cache.remove(key);
System.out.println(Thread.currentThread().getName() + ":缓存删除,key=" + key);
}
public static void main(String[] args) {
// 模拟10个并发查询线程
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> getCache("key" + (finalI % 3)), "查询线程-" + i).start();
}
// 模拟2个更新线程
new Thread(() -> updateCache("key0", "更新后的数据-key0"), "更新线程-1").start();
new Thread(() -> updateCache("key1", "更新后的数据-key1"), "更新线程-2").start();
// 模拟1个删除线程
new Thread(() -> {
try {
Thread.sleep(1000);
removeCache("key2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "删除线程-1").start();
}
}
2. CopyOnWriteArrayList
核心功能:线程安全的ArrayList,采用“写时复制”机制——写入时复制一份新的数组,修改完成后替换原数组,读操作无需加锁,性能优异。
核心功能:线程安全的ArrayList,采用“写时复制”机制——写入时复制一份新的数组,修改完成后替换原数组,读操作无需加锁,性能优异。
适用场景:读多写少的场景(如配置列表、日志列表),写入操作较少,读操作频繁。
完整示例(配置列表场景):
// CopyOnWriteArrayList完整示例:读多写少的配置列表
public class CopyOnWriteArrayListDemo {
// 线程安全的配置列表(写时复制)
private static final CopyOnWriteArrayList<String> configList = new CopyOnWriteArrayList<>();
static {
// 初始化配置
configList.add("配置1:timeout=1000ms");
configList.add("配置2:maxConnection=100");
configList.add("配置3:charset=utf-8");
}
// 读操作:查询配置(多线程可并发读,无锁)
public static void queryConfig() {
System.out.println(Thread.currentThread().getName() + ":查询配置列表,当前配置:");
for (String config : configList) {
System.out.println(" " + config);
try {
Thread.sleep(50); // 模拟读操作耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":配置查询完成\n");
}
// 写操作:添加配置(写时复制,线程安全)
public static void addConfig(String config) {
System.out.println(Thread.currentThread().getName() + ":添加配置:" + config);
configList.add(config);
System.out.println(Thread.currentThread().getName() + ":配置添加完成,当前配置数:" + configList.size() + "\n");
}
// 写操作:修改配置(写时复制)
public static void updateConfig(int index, String newConfig) {
if (index >= 0 && index < configList.size()) {
System.out.println(Thread.currentThread().getName() + ":修改配置,索引" + index + ",旧值:" + configList.get(index));
configList.set(index, newConfig);
System.out.println(Thread.currentThread().getName() + ":配置修改完成,新值:" + newConfig + "\n");
} else {
System.out.println(Thread.currentThread().getName() + ":修改失败,索引无效\n");
}
}
public static void main(String[] args) {
// 模拟10个读线程(高频读)
for (int i = 0; i < 10; i++) {
new Thread(() -> queryConfig(), "读线程-" + i).start();
}
// 模拟2个写线程(低频写)
new Thread(() -> addConfig("配置4:logLevel=info"), "写线程-1").start();
new Thread(() -> updateConfig(0, "配置1:timeout=2000ms"), "写线程-2").start();
}
}
3. BlockingQueue(阻塞队列)
核心功能:支持阻塞的队列,当队列空时,取元素的线程阻塞;当队列满时,存元素的线程阻塞,无需手动处理线程协作,是生产者-消费者模型的核心工具。
常用实现类及场景:
-
ArrayBlockingQueue:有界队列(固定容量),适合对队列容量有明确限制的场景(如固定大小的任务队列);
-
LinkedBlockingQueue:无界队列(默认容量Integer.MAX_VALUE),适合任务量不确定的场景(如消息队列);
核心功能:支持阻塞的队列,当队列空时,取元素的线程阻塞;当队列满时,存元素的线程阻塞,无需手动处理线程协作,是生产者-消费者模型的核心工具。
常用实现类及场景:
-
ArrayBlockingQueue:有界队列(固定容量),适合对队列容量有明确限制的场景(如固定大小的任务队列);
-
LinkedBlockingQueue:无界队列(默认容量Integer.MAX_VALUE),适合任务量不确定的场景(如消息队列);
-
SynchronousQueue:无容量队列,生产者放入元素后必须等待消费者取走,适合一对一的线程协作(如线程池的任务提交)。
完整示例(三种阻塞队列对比,生产者-消费者模型):
// BlockingQueue完整示例:三种队列对比,生产者-消费者模型
public class BlockingQueueDemo {
// 1. ArrayBlockingQueue:有界队列,容量为5
private static final BlockingQueue<String> arrayQueue = new ArrayBlockingQueue<>(5);
// 2. LinkedBlockingQueue:无界队列(默认容量极大)
private static final BlockingQueue<String> linkedQueue = new LinkedBlockingQueue<>();
// 3. SynchronousQueue:无容量队列,生产必须等待消费
private static final BlockingQueue<String> syncQueue = new SynchronousQueue<>();
// 生产者(可指定队列)
private static class Producer implements Runnable {
private final BlockingQueue<String> queue;
private final String queueName;
public Producer(BlockingQueue<String> queue, String queueName) {
this.queue = queue;
this.queueName = queueName;
}
@Override
public void run() {
try {
for (int i = 1; i <= 8; i++) {
String task = "任务" + i;
queue.put(task); // 阻塞式放入,队列满则等待
System.out.println(Thread.currentThread().getName() + ":" + queueName + " 放入" + task + ",当前队列大小:" + queue.size());
Thread.sleep(300); // 模拟生产速度
}
// 放入结束标记
queue.put("END");
System.out.println(Thread.currentThread().getName() + ":" + queueName + " 生产结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费者(可指定队列)
private static class Consumer implements Runnable {
private final BlockingQueue<String> queue;
private final String queueName;
public Consumer(BlockingQueue<String> queue, String queueName) {
this.queue = queue;
this.queueName = queueName;
}
@Override
public void run() {
try {
while (true) {
String task = queue.take(); // 阻塞式取出,队列空则等待
if ("END".equals(task)) {
System.out.println(Thread.currentThread().getName() + ":" + queueName + " 消费结束\n");
break;
}
System.out.println(Thread.currentThread().getName() + ":" + queueName + " 消费" + task + ",当前队列大小:" + queue.size());
Thread.sleep(500); // 模拟消费速度
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 测试1:ArrayBlockingQueue(有界)
System.out.println("=== 测试ArrayBlockingQueue(有界,容量5) ===");
new Thread(new Producer(arrayQueue, "ArrayQueue"), "Array生产者").start();
new Thread(new Consumer(arrayQueue, "ArrayQueue"), "Array消费者").start();
Thread.sleep(5000); // 等待测试1完成
// 测试2:LinkedBlockingQueue(无界)
System.out.println("=== 测试LinkedBlockingQueue(无界) ===");
new Thread(new Producer(linkedQueue, "LinkedQueue"), "Linked生产者").start();
new Thread(new Consumer(linkedQueue, "LinkedQueue"), "Linked消费者").start();
Thread.sleep(5000); // 等待测试2完成
// 测试3:SynchronousQueue(无容量)
System.out.println("=== 测试SynchronousQueue(无容量) ===");
new Thread(new Producer(syncQueue, "SyncQueue"), "Sync生产者").start();
new Thread(new Consumer(syncQueue, "SyncQueue"), "Sync消费者").start();
}
}
六、常用工具类选型总结(避坑关键)
开发中选择JUC工具类,核心是“匹配场景+兼顾性能”,以下是高频场景的选型建议:
-
简单计数器/状态标记:优先用AtomicInteger/AtomicBoolean;海量并发计数用LongAdder;
-
线程安全锁控制:灵活控制用ReentrantLock;读多写少用ReentrantReadWriteLock;读极多写极少用StampedLock;
-
线程协作:主线程等子线程用CountDownLatch;线程对等协作用CyclicBarrier;
-
限流/资源保护:用Semaphore;两个线程数据交换用Exchanger(慎用);
-
并发集合:键值对用ConcurrentHashMap;读多写少列表用CopyOnWriteArrayList;生产者-消费者用BlockingQueue。
七、总结
JUC工具类的核心价值是“简化并发编程,提升性能,避免线程安全问题”,本文梳理的都是开发中最高频的工具类,重点掌握每个工具的“适用场景”——这是面试和实际开发的核心考点。
实际开发中,无需过度追求“高端工具”,根据场景选择最合适的即可:比如简单计数用AtomicInteger,无需上来就用LongAdder;读多写少用读写锁,无需用普通可重入锁浪费性能。
后续会持续更新JUC工具类的源码解析(如AQS底层、CAS原理),关注不迷路,一起夯实Java并发基础!
评论区留言:你在开发中用JUC工具类踩过哪些坑?一起交流避坑经验~
更多推荐


所有评论(0)