java锁,同步器,JUC并发包的工具实际的可以用在那些现实场景
一、同步器
-
CountDownLatch
- 选择等待,直到为0:让线程等待直到持续减一计数器归零,这个等待的线程既可以是主线程,也可以是子线程。
- 方法:它的构造方法,会传入一个 count 值,用于计数。当一个线程调用await方法时,就会阻塞当前线程。每当有线程调用一次 countDown 方法时,计数就会减 1。当 count 的值等于 0 的时候,被阻塞的线程才会继续运行。
- 实际场景
-
服务启动依赖检查:主线程需要等待所有组件(数据库连接、缓存加载、配置文件读取)初始化完成后,才能启动服务。
CountDownLatch latch = new CountDownLatch(3); // 组件1初始化完成后调用 latch.countDown() // 组件2、3同理... latch.await(); // 主线程阻塞,直到计数器归零 startServer(); // 所有组件就绪后启动服务
-
测试并发场景:模拟高并发请求时,确保所有线程就绪后再同时发起请求。
CountDownLatch startLatch = new CountDownLatch(1); for (int i = 0; i < threadCount; i++) { new Thread(() -> { startLatch.await(); // 所有线程在此等待 sendRequest(); // 收到信号后同时发起请求 }).start(); } Thread.sleep(5000); // 模拟准备时间 startLatch.countDown(); // 释放所有线程
-
并行任务汇总:主线程需要等待所有子任务完成后再汇总结果。
CountDownLatch taskLatch = new CountDownLatch(taskCount); for (Task task : tasks) { executor.submit(() -> { try { task.execute(); } finally { taskLatch.countDown(); } }); } taskLatch.await(); // 等待所有任务结束 generateSummaryReport();
- 火箭发射,工程师需完成10项检查(每个检查完成后
countDown()
),指挥员(主线程)在控制台await()
,直到所有检查通过才按下发射按钮。 - 总统选举,等到每个选民投出一票,总统才能上任。
-
-
CyclicBarrier
- 相互等待,同时执行:让一组线程相互等待到屏障点「某个线程到达某个点了,执行await()被阻塞等待」
- 方法:
- 第一个构造的参数,指的是需要几个线程一起到达,才可以使所有线程取消等待。第二个构造,额外指定了一个参数,用于在所有线程达到屏障时,优先执行 barrierAction
- 实际场景:
- 多轮次游戏同步:游戏服务器等待所有玩家完成一局操作后,再开始下一轮。
CyclicBarrier roundBarrier = new CyclicBarrier(playerCount); while (gameRunning) { for (Player player : players) { executor.execute(() -> { player.makeMove(); // 玩家操作 roundBarrier.await(); // 等待所有玩家操作结束 }); } }
- 数据分批次处理:将大数据分片,多个线程处理完当前批次后,每个阶段的计算都依赖于前一阶段的结果,才能执行下一批。在并行计算中的应用
CyclicBarrier batchBarrier = new CyclicBarrier(threadCount); while (hasNextBatch()) { for (int i = 0; i < threadCount; i++) { new Thread(() -> { processBatchData(); batchBarrier.await(); // 等待本批次所有线程处理完成 }).start(); } }
- 多轮次游戏同步:游戏服务器等待所有玩家完成一局操作后,再开始下一轮。
-
Semaphore
- 控制同时访问资源的线程数量
- 方法
- 构造器: boolean 值的参数,控制抢锁是否是公平
public Semaphore(int permits) { sync = new NonfairSync(permits); } public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
- semaphore.acquire(); //获得令牌
- semaphore.release(); //释放令牌
- 构造器: boolean 值的参数,控制抢锁是否是公平
- 实际场景:
- 限流:
- 接口限流:防止突发流量压垮第三方API(如支付接口),令牌桶算法。
Semaphore rateLimiter = new Semaphore(100); // 每秒100个请求 void callExternalAPI() { if (rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) { sendRequest(); } else { throw new RateLimitExceededException(); } }
- 数据库连接池:限制同时使用的数据库连接数,避免资源耗尽。
Semaphore dbSemaphore = new Semaphore(10); // 最多10个连接 void queryDatabase() { dbSemaphore.acquire(); // 获取许可证 try { executeSQL(); } finally { dbSemaphore.release(); } // 释放许可证 }
- 接口限流:防止突发流量压垮第三方API(如支付接口),令牌桶算法。
- 生活场景:
- 停车场管理系统:模拟有限车位,车辆进入/离开时占用/释放信号量。第三方
Semaphore parkingLot = new Semaphore(50); // 50个车位 void carEnter() { parkingLot.acquire(); // 获取车位 parkCar(); } void carExit() { leaveParking(); parkingLot.release(); // 释放车位 }
- 阿萨德
- 限流:
-
Exchanger(交换器)
- 严格2个线程
- 双线程数据交换(如生产者-消费者缓冲区交换)
-
生产者填满缓冲区 → 交换给消费者
消费者处理数据 → 返回空缓冲区给生产者
避免使用共享队列,实现直接传递// 两个线程交换数据 :cite[1] import java.util.concurrent.Exchanger; public class DataExchangeExample { private static final Exchanger<String> exchanger = new Exchanger<>(); public static void main(String[] args) { new Thread(() -> { try { String dataA = "Data from Thread-A"; String received = exchanger.exchange(dataA); // 阻塞直到交换 System.out.println("Thread-A收到: " + received); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { String dataB = "Data from Thread-B"; String received = exchanger.exchange(dataB); System.out.println("Thread-B收到: " + received); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }
输出:Thread-A收到: Data from Thread-B Thread-B收到: Data from Thread-A
-
-
流水线处理系统
-
-
线程A处理阶段1 → 交换中间结果给线程B
-
线程B处理阶段2 → 返回处理结果
-
-
-
校对/校验系统
-
两个线程独立计算同一份数据
-
在交换点比对计算结果是否一致
-
-
Phaser(移相器)
- 关键点:
arriveAndAwaitAdvance()
线程阻塞进入同步阶段,直到所有线程都到同步阶段,自动开始下一阶段,onAdvance()
定义阶段结束条件,直到arriveAndDeregister的线程的线程数量满足结束条件,整体结束。 - 持动态增减线程,动态注册(1-N个线程),随时注册/注销参与者
-
多阶段游戏/仿真系统
-
游戏关卡:准备阶段 → 战斗阶段 → 结算阶段
-
玩家可随时加入/退出游戏
-
package dto.Phaser; import java.util.concurrent.Phaser; public class GameServer { private static final Phaser phaser = new Phaser(1) { // 主线程注册 @Override protected boolean onAdvance(int phase, int parties) { System.out.println("===== 主线程阶段" + phase + "完成 =====\n"); return phase >= 2; // 2阶段后终止 } }; public static void main(String[] args) { startGame(); } static void startGame() { // 阶段0:准备阶段 System.out.println("游戏准备中..."); spawnPlayers(3); // 加入3个玩家 System.out.println("等待所有玩家准备,主线程等待"); phaser.arriveAndAwaitAdvance(); // 等待所有玩家准备 // 阶段1:战斗阶段 System.out.println("\n战斗开始!"); spawnPlayers(2); // 中途加入2个玩家 System.out.println("等待所有玩家战斗阶段,主线程等待"); phaser.arriveAndAwaitAdvance(); // 阶段2:结算阶段 System.out.println("\n战斗结束,结算奖励,主线程退出"); phaser.arriveAndDeregister(); // 主线程退出 } static void spawnPlayers(int count) { for (int i = 0; i < count; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } int playerId = phaser.getRegisteredParties(); new Thread(() -> { phaser.register(); // 动态注册玩家 System.out.println("玩家" + playerId + "加入游戏"); // 阶段0:准备 doAction("准备资源,子线程等待"); phaser.arriveAndAwaitAdvance(); // 阶段1:战斗 doAction("攻击敌人,子线程等待"); phaser.arriveAndAwaitAdvance(); // 阶段2:结算 doAction("领取奖励,子线程退出"); phaser.arriveAndDeregister(); // 注销玩家 }).start(); } } static void doAction(String action) { try { System.out.println(Thread.currentThread().getName() + "执行: " + action); Thread.sleep((long) (Math.random() * 2000)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } 游戏准备中... 玩家1加入游戏 Thread-0执行: 准备资源,子线程等待 玩家2加入游戏 Thread-1执行: 准备资源,子线程等待 等待所有玩家准备,主线程等待 玩家3加入游戏 Thread-2执行: 准备资源,子线程等待 ===== 主线程阶段0完成 ===== Thread-2执行: 攻击敌人,子线程等待 Thread-1执行: 攻击敌人,子线程等待 Thread-0执行: 攻击敌人,子线程等待 战斗开始! 玩家4加入游戏 Thread-3执行: 准备资源,子线程等待 等待所有玩家战斗阶段,主线程等待 玩家5加入游戏 Thread-4执行: 准备资源,子线程等待 ===== 主线程阶段1完成 ===== Thread-4执行: 攻击敌人,子线程等待 Thread-3执行: 攻击敌人,子线程等待 Thread-1执行: 领取奖励,子线程退出 Thread-2执行: 领取奖励,子线程退出 战斗结束,结算奖励,主线程退出 Thread-0执行: 领取奖励,子线程退出 ===== 主线程阶段2完成 ===== Thread-4执行: 领取奖励,子线程退出 Thread-3执行: 领取奖励,子线程退出
-
-
科学计算工作流
-
阶段1:数据加载 → 阶段2:并行计算 → 阶段3:结果汇总
-
动态增减计算节点
-
-
分布式任务协调
-
主任务分发给工作节点
-
等待所有节点完成当前阶段 → 进入下一阶段
-
节点故障时自动注销
-
-
特性 Exchanger Phaser 参与者数量 严格2个线程 动态注册(1-N个线程) 同步方向 双向数据交换 多阶段单向屏障 重用性 可重复交换 自动重置阶段计数器 动态调整 ❌ 固定2方 ✅ 随时注册/注销参与者 适用场景 双线程流水线 多阶段任务(如游戏关卡/科学计算) 复杂任务协调 ❌ ✅ 支持分层、终止条件、回调函数
- 关键点:
二、基础锁 (Locks)
-
synchronized
关键字 (内置锁)
- 延迟初始化一个昂贵的资源:确保只有一个线程能执行初始化代码(配合
volatile
解决可见性问题)。- 初始化单例模式
- 初始化数据库连接池
- 保护共享集合
ConcurrentHashMap:子方法
- HashMap:子方法
- 简单对象状态保护
- 延迟初始化一个昂贵的资源:确保只有一个线程能执行初始化代码(配合
-
ReentrantLock (可重入锁):
- 死锁避免策略,尝试获取锁 (tryLock)
- 使用
tryLock()
设置超时,如果线程 A 在一定时间内无法获取 L2,它可以释放 L1 并回退/重试,避免死锁僵局。死锁避免策略。线程 A 持有锁 L1,尝试获取锁 L2;线程 B 持有锁 L2,尝试获取锁 L1。
- 使用
- 公平锁:资源分配需要严格按请求顺序进行,避免线程饥饿。
- 比如一个低延迟的交易系统中,保证先到的订单请求优先获得处理权。
- 可中断的锁获取 (lockInterruptibly)
- 等待该锁的其他线程可以使用
lockInterruptibly()
来获取锁,这样在等待过程中如果被中断(用户取消),可以立即响应中断,而不是一直傻等。 - private final ReentrantLock lock = new ReentrantLock();
- lock.lockInterruptibly(); // 可中断获取锁
- thread2.interrupt(); // 中断线程 thread2 的等待
- catch (InterruptedException e) 捕获thread2的中断异常。
- 场景:一个长时间运行的任务持有锁,用户可能想取消该任务。
- 需要绑定多个条件 (Condition)
- 实现一个有界阻塞队列。
- 队列满时,put 操作需要等待 (notFull.await());队列空时,get 操作需要等待 (notEmpty.await())。ReentrantLock 可以创建多个 Condition 对象 (notFull, notEmpty) 来精确控制不同条件下的等待/唤醒,比 wait()/notifyAll() 更灵活高效。
// 有界阻塞队列(ArrayBlockingQueue 原理) // 当线程需要基于不同的条件挂起或唤醒时(例如生产者消费者模型中的“非满”和“非空”条件),Condition 允许多个独立的等待队列共存。 public class BoundedBlockingQueue { private final ReentrantLock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); // 队列非满条件 private final Condition notEmpty = lock.newCondition(); // 队列非空条件 private final Object[] items; private int count; public BoundedBlockingQueue(int capacity) { items = new Object[capacity]; } // 生产者:入队 public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); // 队列满时挂起生产者(等待notFull条件) items[count++] = x; notEmpty.signal(); // 入队后唤醒消费者(满足notEmpty条件) } finally { lock.unlock(); } } // 消费者:出队 public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); // 队列空时挂起消费者(等待notEmpty条件) Object x = items[--count]; notFull.signal(); // 出队后唤醒生产者(满足notFull条件) return x; } finally { lock.unlock(); } } }
- 状态依赖的线程协调:当线程需要等待系统进入特定状态才执行时(如任务必须等待资源初始化完成):
class ResourceManager { private boolean isReady = false; private final ReentrantLock lock = new ReentrantLock(); private final Condition resourceReady = lock.newCondition(); public void initResource() { lock.lock(); try { // 初始化资源... isReady = true; resourceReady.signalAll(); // 唤醒所有等待线程 } finally { lock.unlock(); } } public void doTask() throws InterruptedException { lock.lock(); try { while (!isReady) { resourceReady.await(); // 等待资源就绪 } // 执行任务... } finally { lock.unlock(); } } }
- 线程按顺序执行:控制线程执行顺序(如 A→B→C):
class OrderedExecutor { private int flag = 1; // 1:允许A执行, 2:允许B执行, 3:允许C执行 private final ReentrantLock lock = new ReentrantLock(); private final Condition condA = lock.newCondition(); private final Condition condB = lock.newCondition(); private final Condition condC = lock.newCondition(); public void executeA() throws InterruptedException { lock.lock(); try { while (flag != 1) condA.await(); System.out.println("A"); flag = 2; condB.signal(); // 唤醒B } finally { lock.unlock(); } } public void executeB() throws InterruptedException { lock.lock(); try { while (flag != 2) condB.await(); System.out.println("B"); flag = 3; condC.signal(); // 唤醒C } finally { lock.unlock(); } } // executeC() 类似... }
-
能力 Condition
Object.wait()/notify()
多等待队列 ✅ 支持多个条件(如 notFull
,notEmpty
)❌ 仅1个等待队列 精准唤醒 ✅ signal()
唤醒同一条件的线程❌ notify()
随机唤醒一个超时等待 ✅ awaitNanos()
,awaitUntil()
❌ 仅支持无限等待 响应中断 ✅ await()
响应中断✅ wait()
响应中断公平性支持 ✅ 与 ReentrantLock
公平策略绑定❌ 无公平性控制 -
关键实践原则
-
总是用 while 检查条件
while (!condition) {
cond.await(); // 防止虚假唤醒(Spurious Wakeup)
} -
先获取锁再调用 await/signal,未持有锁时调用会抛 IllegalMonitorStateException。
-
优先使用 signal() 而非 signalAll(),避免无效唤醒(除非明确需要唤醒所有线程)。
-
- 队列满时,put 操作需要等待 (notFull.await());队列空时,get 操作需要等待 (notEmpty.await())。ReentrantLock 可以创建多个 Condition 对象 (notFull, notEmpty) 来精确控制不同条件下的等待/唤醒,比 wait()/notifyAll() 更灵活高效。
- 实现一个有界阻塞队列。
- 死锁避免策略,尝试获取锁 (tryLock)
-
StampedLock
-
StampedLock 有三种读写方法:
- readLock:读锁,用于多线程并发读取共享资源。
- writeLock:写锁,用于独占写入共享资源。
- tryOptimisticRead:读乐观锁,用于在不阻塞其他线程的情况下尝试读取共享资源。
-
// 创建 StampedLock 实例 StampedLock lock = new StampedLock(); // 获取乐观读锁 long stamp = lock.tryOptimisticRead(); // 读取共享变量 if (!lock.validate(stamp)) { // 检查乐观读锁是否有效 stamp = lock.readLock(); // 如果乐观读锁无效,则获取悲观读锁 try { // 重新读取共享变量 } finally { lock.unlockRead(stamp); // 释放悲观读锁 } } // 获取悲观读锁 long stamp = lock.readLock(); try { // 读取共享变量 } finally { lock.unlockRead(stamp); // 释放悲观读锁 } // 获取写锁 long stamp = lock.writeLock(); try { // 写入共享变量 } finally { lock.unlockWrite(stamp); // 释放写锁 }
-
三、JUC 并发工具类
-
ExecutorService
/ThreadPoolExecutor
(线程池):-
场景:几乎所有需要异步或并行执行任务的场景
-
例子:
-
Web 服务器:使用线程池处理 HTTP 请求。每个请求由一个线程处理,线程池复用线程,避免频繁创建销毁线程的开销。
-
后台任务处理:发送邮件、生成报表、数据清洗等耗时操作,提交给线程池异步执行,不阻塞主线程。
-
并行计算:将大任务拆分成小任务,提交给线程池并行执行,提高计算速度。
-
-
-
-
Future
/CompletableFuture
(异步结果):-
场景:需要获取异步任务结果或进行链式异步调用
-
例子:
-
Future
: 向线程池提交一个计算任务,主线程在需要结果时调用future.get()
获取(可能阻塞等待)。 -
CompletableFuture
: 组合多个异步操作。例如:先异步查询用户信息(cf1)
, 查询到后根据用户信息异步查询订单(cf2 = cf1.thenCompose(user -> getOrdersAsync(user)))
, 两个都完成后异步发送通知(cf1.thenCombine(cf2, (user, orders) -> sendNotification(user, orders)))
。构建非阻塞、响应式流程。- 要求所有请求成功没成功3s返回
ExecutorService executor = Executors.newCachedThreadPool(); List<CompletableFuture<Result>> futures = new ArrayList<>(); // 1. 提交所有请求任务 for (Request request : requests) { futures.add(CompletableFuture.supplyAsync(() -> { try { return processRequest(request); // 实际业务处理 } catch (Exception e) { return Result.failure(e); // 捕获所有异常 } }, executor)); } // 2. 创建总超时控制 CompletableFuture<Void> allDone = CompletableFuture.allOf( futures.toArray(new CompletableFuture[0]) ); try { // 3. 关键:设置3秒全局超时 allDone.get(3, TimeUnit.SECONDS); // 阻塞直到所有完成或超时 } catch (TimeoutException e) { // 4. 超时处理:取消未完成的任务 futures.forEach(f -> f.cancel(true)); // true表示中断线程 } catch (InterruptedException | ExecutionException e) { // 其他异常处理 } // 5. 收集结果(已完成的任务返回结果,未完成的返回超时标识) List<Result> results = futures.stream().map(f -> { return f.isDone() ? f.join() : Result.timeout(); }).collect(Collectors.toList());
方案优势
能力 实现效果 严格3秒返回 无论成功失败,3秒必响应 资源控制 自动取消超时任务释放资源 异常安全 统一处理业务异常和超时 结果完整性 返回所有请求状态(成功/失败/超时) 现代API 避免 synchronized
/wait()
等底层操作对比其他同步器方案
同步器 问题 本方案优势 CountDownLatch
需手动计数,无法取消阻塞线程 自动任务取消 CyclicBarrier
超时处理复杂,不适合动态任务 简洁超时控制 Future.get()
单个超时控制,无法批量管理 统一批量管理 Object.wait()
需配合synchronized,易死锁 无锁安全
- 要求所有请求成功没成功3s返回
-
-
-
-
ConcurrentHashMap
(并发哈希表):-
场景:高并发读写的键值对存储
-
例子:
-
全局缓存:存储应用配置、热点数据(如商品信息)。支持高并发读取和更新(如使用
compute
/merge
原子更新)。 -
会话存储:Web 应用中存储用户 Session 对象。
-
计数器:利用
compute
或merge
方法实现线程安全的计数器(如map.merge(key, 1, Integer::sum)
)。
-
-
-
-
CopyOnWriteArrayList
/CopyOnWriteArraySet
(写时复制集合): 线程安全集合之CopyOnWriteArrayList详解-
场景:读多写少,且要求遍历时快照一致性
-
例子:
-
监听器列表:事件源维护一个监听器列表。注册/注销监听器(写)操作较少,触发事件(遍历通知所有监听器 - 读)操作频繁。
CopyOnWriteArrayList
保证遍历时看到的是注册/注销操作发生时刻的完整快照,避免了遍历时加锁,且写操作不影响正在进行的遍历。 -
只读为主的白名单/黑名单:名单更新不频繁,但查询非常频繁。
-
-
-
-
BlockingQueue
及其实现 (阻塞队列):-
场景:生产者-消费者模型
-
例子:
-
ArrayBlockingQueue
/LinkedBlockingQueue
: 任务队列。线程池内部的核心组件。生产者线程提交任务(offer()
/put()
),消费者线程(工作线程)从队列中获取任务(poll()
/take()
)执行。队列满时生产者阻塞,队列空时消费者阻塞。 -
SynchronousQueue
: 直接传递。生产者必须等待消费者准备好接收(调用take()
),才能put()
成功。实现线程间直接、无缓冲的手递手传递,常用于要求高吞吐、低延迟且消费者能力足够强的线程池 (Executors.newCachedThreadPool
)。 -
PriorityBlockingQueue
: 带优先级的任务队列。例如,处理不同优先级的日志消息或订单。- 线程安全通过
ReentrantLock
保证,扩容时使用 CAS 自旋锁优化 -
方法 行为 offer(E e)
插入元素(自动扩容) take()
阻塞获取队首元素(队列空时阻塞) poll()
非阻塞获取队首元素(队列空返回 null
) -
使用场景
-
任务优先级调度:高优先级任务优先执行(如紧急告警)。
-
资源分配:按优先级分配资源(如带宽控制)
-
- 线程安全通过
-
DelayQueue
: 延迟任务调度。元素只有在其指定的延迟时间过后才能被取出。实现定时任务调度器、缓存过期失效等。-
方法 行为 offer(E e)
插入元素(永不阻塞) take()
阻塞直到获取到期元素 poll()
非阻塞获取到期元素(无到期元素返回 null
)poll(timeout)
超时阻塞获取元素
-
-
-
-
-
AtomicXxx
(原子类):-
场景:无锁状态更新、计数器、标志位
-
例子:
-
AtomicInteger
/AtomicLong
: 高性能计数器(如统计访问量、生成全局唯一递增 ID -incrementAndGet()
)。 -
AtomicBoolean
: 简单的开关标志(如isRunning
)。 -
AtomicReference
: 原子更新对象引用(如实现无锁栈push/pop
的核心)。 -
LongAdder
/DoubleAdder
(JDK8+): 超高并发下的累加器(如统计点击数),性能远优于AtomicLong
,通过分散热点减少竞争。
-
-
-
总结与选择建议
-
简单互斥: 首选
synchronized
。如果够用且性能可接受,就用它,代码简洁。 -
高级锁需求 (尝试锁、公平锁、可中断锁、多条件): 使用
ReentrantLock
。 -
等待线程组完成一次任务: 用
CountDownLatch
(一次性)。 -
等待线程组完成多个阶段的任务: 用
CyclicBarrier
(可重用) 或更灵活的Phaser
。 -
控制并发访问资源数量: 用
Semaphore
(资源池、限流)。 -
线程间交换数据缓冲区: 考虑
Exchanger
(两方)。 -
异步执行任务: 必用 线程池 (
ExecutorService
)。 -
获取异步结果、编排异步操作: 用
Future
(简单) 或CompletableFuture
(强大组合)。 -
高并发键值存储: 必用
ConcurrentHashMap
。 -
读多写少且需遍历快照一致性的集合: 用
CopyOnWriteArrayList/CopyOnWriteArraySet
。 -
生产者-消费者: 必用
BlockingQueue
(选具体实现)。 -
无锁计数器/状态更新: 首选
AtomicXxx
类或LongAdder
。