Thread是线程载体和执行控制器,Runnable仅为任务契约;前者绑定OS线程、重量级,后者无状态、可复用、支持函数式组合与线程池集成。

Thread 和 Runnable 的本质区别在哪
根本不是“哪个更好用”的问题,而是职责分离:Thread 是线程的载体和执行控制器,Runnable 只是任务契约。你不能把 Runnable 当作线程启动,必须交给 Thread 或线程池来运行。
常见错误是直接 new Runnable 并调用 run() —— 这只是普通方法调用,完全不走多线程流程;正确做法是 new Thread(runnable).start()。
-
Thread继承自Object,本身是重量级类,每个实例都绑定一个 OS 线程资源 -
Runnable是函数式接口,只定义void run(),无状态、可复用、适合组合(比如包装日志、计时、异常捕获) - 继承
Thread会浪费单继承机会;实现Runnable更灵活,也兼容ExecutorService
为什么 Executors.newFixedThreadPool 不该在生产环境直接用
它底层用的是无界队列 LinkedBlockingQueue,任务持续涌入时,队列无限增长,极易 OOM。JDK 自己在文档里就标了 @Deprecated(从 Java 21 起正式废弃)。
替代方案不是“换一个工厂方法”,而是明确控制队列容量和拒绝策略:
立即学习“Java免费学习笔记(深入)”;
- 用
ThreadPoolExecutor显式构造,传入有界队列(如new ArrayBlockingQueue(100)) - 拒绝策略别用默认的
AbortPolicy(抛RejectedExecutionException),根据场景选CallerRunsPolicy或自定义丢弃+告警逻辑 - 线程名务必自定义:通过
ThreadFactory设置前缀,否则堆栈里全是pool-1-thread-1,线上排查时寸步难行
CompletableFuture 和 ExecutorService 搭配使用的坑
CompletableFuture 默认使用 ForkJoinPool.commonPool(),这个池子共享且不可配置。一旦有 CPU 密集型任务长期占用,会拖垮所有用它的异步逻辑(包括 JSON 序列化、Stream 并行操作等)。
真正可控的做法是:
- 所有 IO 型异步操作(如 HTTP 调用、DB 查询)必须指定自定义线程池:
CompletableFuture.supplyAsync(() -> doHttpCall(), ioExecutor)
- 避免混用
thenApply(同步执行)和thenApplyAsync(异步执行)——前者在上游线程中执行,可能阻塞关键路径 - 不要在
handle或whenComplete里做耗时操作,它们默认沿用上游线程,容易导致线程饥饿
ThreadLocal 在线程池里不清理就是内存泄漏
线程池复用线程,而 ThreadLocal 的 Entry 是以线程为 key 的弱引用,但 value 是强引用。如果任务没手动 remove(),value 就一直挂着,GC 不掉。
典型泄漏场景:用 ThreadLocal 格式化时间,又忘了清理。
- 必须在 finally 块或 try-with-resources 中调用
threadLocal.remove() - 更安全的做法是封装工具类,在
Runnable包装器中统一before/after处理 - Spring 的
RequestContextHolder就是靠Filter在请求结束时自动reset(),自己写框架也要照这个思路
线程池 + ThreadLocal 是高频泄漏组合,比 static 引用更隐蔽,因为堆 dump 里看不到明显 GC Root,得看线程栈和 ThreadLocalMap 的实际 entry 数量。










