直接用 new thread() 会拖慢系统响应,因其每次创建都触发栈分配、线程调度和 os 上下文切换,高并发下开销远超任务本身;jdk 并发工具通过分层封装实现资源复用,非语法糖。

为什么直接用 new Thread() 会拖慢系统响应
手动创建线程看似简单,但每次 new Thread().start() 都要分配栈内存、触发线程调度、经历 OS 级上下文切换——高并发下这比任务本身还耗时。JDK 的 java.util.concurrent 工具类本质是把“线程生命周期管理”和“任务调度逻辑”做了分层封装,不是语法糖,而是对资源复用的强制约束。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 永远优先用
ExecutorService替代裸线程,哪怕只跑一个任务; -
Executors.newFixedThreadPool(4)看似方便,但它的LinkedBlockingQueue默认无界,任务堆积时会 OOM;应显式传入有界队列,比如new ThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(100)); - 不要在
Runnable或Callable中吞掉异常——未捕获的异常会让工作线程静默终止,池子慢慢“残废”。务必在run()内加try-catch并记录日志。
ConcurrentHashMap 的 key 为什么不能用可变对象
这不是线程安全问题,而是哈希一致性问题:ConcurrentHashMap 在扩容或查找时依赖 key 的 hashCode() 和 equals()。如果 key 是自定义类且字段被修改,后续 get() 可能落到错误的 bin,返回 null 而不报错。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- key 类必须满足:不可变(
final字段 + 无 setter)、正确重写hashCode()和equals(); - 常见踩坑:用
StringBuilder或ArrayList当 key——它们的hashCode()会随内容变,绝对禁止; - 如果业务上真需要“动态 key”,改用
Map<immutablekey v></immutablekey>,其中ImmutableKey是自己写的不可变包装类,构造时就固化所有字段。
用 CountDownLatch 等待多个异步结果时,为什么总卡住不往下走
典型现象是主线程调用 latch.await() 后永远阻塞。根本原因只有两个:要么 countDown() 根本没被调用(比如异步任务抛异常退出),要么被多次调用导致计数器减到负数(CountDownLatch 不校验负值,但行为已不可控)。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 确保每个异步分支都包裹
try-finally,把countDown()放在finally块里; - 避免在回调中重复调用
countDown(),尤其当使用第三方 SDK(如 Netty 的ChannelFutureListener)时,某些失败场景会触发多次回调; - 调试时加一行日志:
System.out.println("count: " + latch.getCount());,确认是否真归零; - 如果等待逻辑复杂(比如部分失败仍要继续),改用
CyclicBarrier或CompletableFuture.allOf()更清晰。
CompletableFuture 链式调用里,thenApply 和 thenCompose 到底该选哪个
区别不在“异步”与否,而在返回值类型:thenApply 接收 T → U,返回 CompletableFuture<u></u>;thenCompose 接收 T → CompletableFuture<u></u>,返回 CompletableFuture<u></u>——它自动展平一层嵌套,避免出现 CompletableFuture<completablefuture>></completablefuture>。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 下游方法返回的是普通对象,用
thenApply;返回的是另一个CompletableFuture(比如又调了一次 HTTP 请求),必须用thenCompose; - 漏用
thenCompose的后果很隐蔽:编译能过,运行时join()拿到的是未完成的内部 future,再join()一次才得到真实结果——多一层阻塞,且容易漏处理异常; - 别为了“链得好看”硬套
thenCompose:如果下游只是做数据转换(比如String::toUpperCase),用thenApply更轻量。
并发工具类不是性能银弹,它们把复杂性从“怎么写对”转移到“怎么配对”。线程池大小、队列容量、超时时间这些参数没有通用值,必须结合 CPU 密集型/IO 密集型特征、平均响应时间和错误率压测后反推。最常被忽略的一点:所有 ExecutorService 都必须显式调用 shutdown() 或 shutdownNow(),否则 JVM 无法正常退出。









