在Java中Runnable接口如何创建线程_Java多线程入门解析

Runnable接口不创建线程,仅定义任务;真正创建并启动线程的是Thread类或线程池;直接调用run()无并发效果,须用start();匿名类、Lambda、独立类三种实现方式各适配不同场景。

Runnable接口本身不创建线程,它只定义任务

这是最常被误解的一点:Runnable 是一个函数式接口,只规定了 run() 方法签名,它不启动线程、不管理生命周期、也不持有线程资源。真正创建并启动线程的是 Thread 类或线程池。

常见错误现象:直接调用 myRunnable.run() —— 这只是普通方法调用,仍在当前线程执行,**完全不并发**。

  • 正确做法是把 Runnable 实例传给 Thread 构造器,再调用 thread.start()
  • start() 才会触发 JVM 创建新线程并回调 run()
  • 重复调用 start() 会抛出 IllegalThreadStateException

三种主流写法及适用场景

实际开发中,Runnable 的实现方式影响可读性、复用性和维护成本。

匿名内部类(适合一次性简单任务):

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread: " + Thread.currentThread().getName());
    }
}).start();

Lambda(Java 8+,推荐用于无状态逻辑):

new Thread(() -> {
    System.out.println("Hello from lambda thread");
}).start();

独立类实现(适合需复用、含状态或需测试的场景):

public class CounterTask implements Runnable {
    private final int count;
    public CounterTask(int count) { this.count = count; }
    @Override
    public void run() {
        for (int i = 0; i < count; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}
// 使用
new Thread(new CounterTask(5)).start();
  • Lambda 不能捕获非 final 或“事实上 final”的变量;若需修改外部变量,用独立类更安全
  • 独立类可被单元测试、可注入依赖、可加日志/监控埋点
  • 匿名类在调试时类名难识别(如

    MyApp$1),不利于排查线程 dump

为什么不用继承 Thread 类而选 Runnable?

这不是风格偏好,而是 Java 语言机制和工程实践的硬约束。

  • Java 不支持多继承,如果类已继承其他父类(如 FrameActivity),就无法再 extends Thread
  • Runnable 更符合“单一职责”:任务逻辑(what)与执行机制(how)分离
  • 线程池(如 ExecutorService)只接受 RunnableCallable,不接受 Thread 子类
  • 直接 new Thread() 容易造成线程资源失控;用 Runnable + 线程池才是生产环境标准做法

容易忽略的线程安全陷阱

很多人以为只要用了 Runnable 就“多线程了”,却没意识到共享数据的风险。

  • 多个 Thread 实例共用同一个 Runnable 对象(尤其用 Lambda 捕获外部变量时),其字段可能被并发修改
  • 即使每个线程 new 一个 Runnable,若它们操作静态变量、单例对象或传入的共享集合,仍需同步
  • System.out.println() 虽然线程安全,但输出可能交错(如 “A1B2C3”),不代表业务逻辑安全
  • 不要在 run() 中吞掉异常:未捕获的 RuntimeException 会导致线程静默终止,且不会传播到主线程

线程真正的复杂点不在怎么启动,而在共享状态的可见性、原子性和有序性——这些靠 Runnable 接口本身解决不了,得靠 synchronizedvolatilejava.util.concurrent 工具类来兜底。