应使用 ExecutorService 线程池替代手动 new Thread(),推荐显式构造 ThreadPoolExecutor 并配置有界队列与拒绝策略;批量任务用 invokeAll() 统一等待;共享状态优先用 AtomicInteger 或 ReentrantLock;ThreadLocal 必须手动 remove() 防泄漏。

用 ExecutorService 替代手动 new Thread() 启动线程
手动 new Thread().start() 在并发量稍大时极易失控:线程数无节制增长、资源耗尽、OOM 频发。真正可控的并发执行必须走线程池。
推荐使用 Executors.newFixedThreadPool(n) 或更安全的显式构造方式:
ExecutorService pool = new ThreadPoolExecutor(
4, 8, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
new ThreadFactoryBuilder().setNameFormat("biz-task-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
-
corePoolSize=4表示常驻线程数,避免频繁创建销毁开销 -
maximumPoolSize=8是峰值容量,配合有界队列防雪崩 -
CallerRunsPolicy在队列满+线程达上限时,由提交线程自己执行任务,起到自然降级作用 - 别用
Executors.newCachedThreadPool()—— 它的无界队列 + 60 秒空闲回收,在突发流量下会疯狂新建线程
并发任务结果聚合:用 invokeAll() 而非循环 submit() + get()
需要等一批任务全部完成并收集返回值?常见错误是逐个 submit() 再逐个 get(),这会串行等待,失去并发意义。
正确做法是批量提交后统一阻塞等待:
立即学习“Java免费学习笔记(深入)”;
KgShop,是国内一款快速/稳定/安全的开源电子商城系统,采用linux,mysql,srutsEX,hibernate,ejb3等技术,Kghop第一版诞生于2010年,经过多年开发,Kgshop系统已拥有快速、稳定、支持大量并发访问等软件特性,是10万人在线的JAVA商城优秀解决方案。KgShop拥有良好的模板机制,易于进行二次开发。Kgshop每一行代码都经过严谨的测试,汇聚大批工程师多年
List> tasks = Arrays.asList( () -> { Thread.sleep(100); return "A"; }, () -> { Thread.sleep(200); return "B"; } ); List > futures = pool.invokeAll(tasks, 5, TimeUnit.SECONDS); // 带超时 List results = futures.stream() .map(f -> { try { return f.get(); } catch (Exception e) { return null; } }) .collect(Collectors.toList());
-
invokeAll()保证所有任务并发启动,且整体超时控制(第二个参数)比单个get(timeout)更可靠 - 若某任务抛异常或超时,对应
Future的get()会抛ExecutionException或TimeoutException,需捕获处理 - 不要在
invokeAll()后再调用shutdown()—— 必须等所有Future处理完,否则可能中断未完成任务
共享状态写操作必须加锁:优先选 ReentrantLock 而非 synchronized
多个线程更新同一个计数器、缓存 Map 或 List,不加锁必然数据错乱。虽然 synchronized 最简单,但实际场景中往往需要更精细的控制。
比如要支持超时获取锁、可中断等待、或按条件唤醒特定线程:
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
counter++;
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("acquire lock timeout");
}
}
-
tryLock(long, TimeUnit)可避免无限阻塞,适合有 SLA 要求的服务 - 用
lockInterruptibly()支持线程被中断时及时退出,比synchronized更灵活 - 对简单原子操作(如计数),优先用
AtomicInteger,它底层基于 CAS,无锁且性能更高 -
synchronized不是不能用,但在需要响应中断、超时、或公平性策略时,ReentrantLock是唯一选择
线程局部变量用 ThreadLocal,但务必手动 remove()
为避免传参污染,常把用户 ID、请求 traceId 存进 ThreadLocal。但 Web 容器(如 Tomcat)用线程池复用线程,不清理会导致内存泄漏和脏数据。
- 永远在业务逻辑结束时调用
threadLocal.remove(),不要依赖set(null) - 在 Filter / Interceptor 中统一
try-finally清理,例如:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
try {
MDC.put("traceId", generateTraceId());
chain.doFilter(req, res);
} finally {
MDC.clear(); // 实际是 ThreadLocal.remove()
}
}
- 自定义
ThreadLocal时,若值对象较大(如缓存的数据库连接),更要警惕内存泄漏 - Spring 的
@Transactional、MyBatis 的SqlSession底层都重度依赖ThreadLocal,所以事务失效、连接复用异常等问题,八成和没清理有关
synchronized 就算并发安全,关键在资源生命周期管理——线程池大小、任务提交方式、锁粒度、上下文清理,每一步漏掉都会在高并发时暴露。









