继承Thread类导致每个实例独占线程资源并重复创建销毁,开销大;实现Runnable接口使任务与线程分离,支持线程复用、减少内存占用和上下文切换,更高效。

继承Thread类和实现Runnable接口在内存模型与运行开销上差异显著,核心在于“谁负责创建线程”以及“任务与线程实体是否分离”。
内存分配模型不同:线程本体 vs 任务载体
继承Thread时,每个子类实例本身就是线程对象——JVM为其分配独立的线程栈、PC寄存器、本地方法栈等完整线程资源;而实现Runnable后,该实例只是普通Java对象(堆中普通对象),不携带任何线程运行时结构,只有被Thread或线程池包装执行时,才触发底层线程资源分配。
- Thread子类实例 = 线程实体 + 任务逻辑 → 每个new都触发内核线程创建 + 栈内存分配(默认1MB左右)
- Runnable实例 = 纯任务数据容器 → 只占堆内存(如含int count仅8字节+对象头),无栈、无线程调度开销
线程复用能力决定实际内存压力
Runnable天然支持多线程共享同一任务实例,而Thread子类无法复用线程对象本身:
- new Thread(new Task()) × 10 → 创建10个独立线程,10份栈内存,10次内核态切换准备
- Task task = new Task(); new Thread(task) × 10 → 仍创建10个线程,但task只有一份堆内存;若改用ExecutorService,还可复用固定数量线程,避免反复创建销毁
- 继承Thread方式无法接入线程池——因为线程池要求提交的是Runnable或Callable,而非Thread子类
共享变量行为暴露底层内存可见性差异
看似是“能不能共享count”的问题,实则是不同对象生命周期导致的内存可见性表现:
立即学习“Java免费学习笔记(深入)”;
- 每个Thread子类实例拥有独立字段副本(如private int count),彼此无关联,自然不共享——这不是同步问题,是对象隔离
- 多个Thread共用一个Runnable实例时,其字段位于堆中同一地址,所有线程操作的是同一个变量——此时才真正触发volatile、synchronized或原子类的必要性
- 注意:Runnable字段若为final或基本类型且不修改,仍可安全共享;一旦可变,就必须考虑JMM(Java Memory Model)的happens-before约束
真实开销对比(以启动100个短任务为例)
假设每个任务执行10ms后结束:
- 继承Thread:约100次线程创建+销毁,每次约50–100KB栈内存分配+释放,频繁触发GC压力与上下文切换(平均每次切换耗时0.5–2μs,100次即50–200μs额外开销)
- 实现Runnable + 单线程Executor:仅1个线程长期存活,任务对象堆内存总占用极小,无重复栈分配,上下文切换次数趋近于0
- 实现Runnable + 固定大小线程池(如4线程):4份栈内存常驻,100个任务排队/并行执行,内存可控,吞吐更稳


















