synchronized仅能实现串行化(并发度=1),控制可控并发度需用semaphore或线程池;避免锁对象不当、i/o阻塞及无效加锁。

Java中用 synchronized 控制大表分批聚合的并发度,本质上是“限制同时执行的线程数”,但要注意:synchronized 本身不直接控制并发度,它只能保证同一时刻只有一个线程进入临界区。若想实现可控的并发数(比如最多 3 个线程并行处理批次),需配合其他机制,synchronized 仅适合极简场景(如严格串行)。下面分情况说明实用做法。
一、synchronized 仅用于串行化(最简单但并发度=1)
如果业务允许完全串行处理(例如数据量不大、强一致性要求极高),可用一个共享锁对象包裹批次处理逻辑:
private final Object batchLock = new Object();
// 每个线程调用此方法处理一个批次
public void processBatch(List<record> batch) {
synchronized (batchLock) {
// 这里执行聚合逻辑:JDBC批量插入、Redis累加、Flink sink 等
aggregateAndPersist(batch);
}
}</record>
✅ 优点:无额外依赖,线程安全清晰。
❌ 缺点:并发度恒为 1,无法发挥多核优势,大表处理会显著变慢。
二、用 Semaphore 替代 synchronized 实现 N 路并发
真正需要“控制并发度”时,Semaphore 是更合适的选择——它能精确放行指定数量的线程:
- 初始化
new Semaphore(3)表示最多 3 个线程同时执行 - 每个批次处理前调用
semaphore.acquire()(阻塞直到有许可) - 处理完成后必须在
finally中调用semaphore.release()
示例:
private final Semaphore semaphore = new Semaphore(3);
public void processBatch(List<record> batch) {
try {
semaphore.acquire(); // 等待许可
aggregateAndPersist(batch); // 执行聚合
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 归还许可
}
}</record>
⚠️ 注意:不要在 synchronized 块内嵌套 acquire,否则可能死锁或降低吞吐;Semaphore 本身已线程安全。
三、结合线程池 + 批次调度,兼顾资源与并发可控性
生产环境推荐组合方案:固定大小线程池 + Semaphore(或直接靠线程池限流) + 分批提交任务:
- 创建
Executors.newFixedThreadPool(4),天然限制最大并发线程数 - 将大表按主键/时间范围切分为多个
Callable<result></result>任务 - 提交所有任务,用
executor.invokeAll(tasks)或CompletableFuture.allOf等待完成
关键点:聚合操作本身(如写库)若存在共享资源竞争(如单个 MySQL 连接、全局计数器),才需在内部加 synchronized 或 AtomicLong;但并发控制应由线程池或 Semaphore 承担,而非靠 synchronized 抢锁。
四、避免常见误区
不要对每个批次 new Object() 加锁——这等于没锁,各批次互不影响;
不要用 this 或 Class.class 作为锁对象去控并发度——易引发意外锁竞争,影响其他无关逻辑;
不要在 synchronized 块中做耗时 I/O(如远程 HTTP、慢 SQL)——会把锁占太久,拖垮整体吞吐;
聚合结果若需合并(如 sum/count),优先用线程局部变量 + 最终归并,而非全程同步累加。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











