Java中两种Thread实现方式的技术差异与最佳实践咨询
Java两种Runnable线程创建方式的技术差异解析
先看你给出的两种创建线程的代码:
// 方式1:直接传入实现Runnable的类实例 Thread t1 = new Thread(new NumberGenerator(100)); // 方式2:使用方法引用生成Runnable Thread t2 = new Thread(new NumberGenerator(100)::run);
对应的完整代码:
public class NumberGenerator implements Runnable { private int limit; public NumberGenerator(int limit) { this.limit = limit; } @Override public void run() { for(int i = 0; i < limit; i++) { System.out.println(i); } } } public class Main { public static void main(String[] args) { Thread t1 = new Thread(new NumberGenerator(100)); Thread t2 = new Thread(new NumberGenerator(100)::run); t1.start(); t2.start(); } }
下面针对你的问题逐一解答:
一、二者是否都属于Runnable的子类?
首先明确:Runnable是接口而非类,准确来说是判断是否为Runnable的实现类实例:
- 方式1:
new NumberGenerator(100)是NumberGenerator类的实例,该类直接实现了Runnable接口,本身就是Runnable的实现类实例。 - 方式2:
new NumberGenerator(100)::run是Java方法引用语法,编译器会自动生成一个匿名的Runnable实现类并创建实例。这个匿名类和NumberGenerator无继承关系,仅持有NumberGenerator实例的引用,调用自身run方法时会委托给NumberGenerator的run方法。
二、判定对象为Runnable的条件是什么?
只要对象所属的类实现了java.lang.Runnable接口,也就是类中存在符合public void run()签名的方法(接口强制要求),这个对象就会被判定为Runnable类型,Java多态机制会自动识别这种类型匹配。
三、两种方式的技术差异
1. 对象结构与关系
- 方式1:仅创建1个
NumberGenerator实例,该实例同时承担业务逻辑和Runnable角色。 - 方式2:创建2个对象:
NumberGenerator实例 + 编译器生成的匿名Runnable实例,后者依赖前者完成方法调用。
2. 方法调用链路
- 方式1:调用链路更短:
Thread.start()→Thread.run()→NumberGenerator.run() - 方式2:多一层委托:
Thread.start()→Thread.run()→ 匿名Runnable.run() →NumberGenerator.run()
3. 内存开销
方式2比方式1多创建一个匿名类实例,带来额外内存开销(多数场景可忽略,但高频创建时会有影响)。
4. 序列化兼容性
如果NumberGenerator实现了Serializable接口,方式1的实例可直接序列化;而方式2的匿名Runnable类默认未实现Serializable,会导致序列化失败,无法用于需要序列化的场景(如分布式缓存、远程调用)。
四、最佳实践
- 优先使用方式1:代码更直观,减少不必要的对象创建,调用链路更高效,同时避免序列化等潜在问题,可读性更强。
- 方式2的适用场景:当需要将未实现Runnable接口的类中的无参void方法作为线程执行逻辑时,比如某类有
public void doTask()方法,用obj::doTask快速转成Runnable;但如果类本身已实现Runnable,完全没必要用方法引用多此一举。
内容的提问来源于stack exchange,提问作者happy
相关产品推荐
相关产品推荐

