源本科技 | 码上会

Java Runnable 接口

2026/01/29
40
0

学习目标

  • 理解 Runnable 接口的作用及其在多线程编程中的核心地位

  • 掌握通过实现 Runnable 创建线程的标准步骤

  • 能够使用 Lambda 表达式简化 Runnable 的编写

  • 了解如何结合 Executor 框架高效管理线程任务

  • 掌握 Runnable 中异常处理的正确方式

  • 明确 Runnable 与直接继承 Thread 类的优劣对比


什么是 Runnable 接口

Runnable 是 Java 标准库 java.lang 包中的一个函数式接口,用于定义一个可以被线程执行的任务。它将任务逻辑(做什么)与线程执行机制(如何执行)解耦,是实现多线程的推荐方式。

接口声明

public interface Runnable {
    void run();
}
  • 只包含一个抽象方法 run()

  • run() 方法中编写线程要执行的具体代码

  • 由于只有一个抽象方法,Runnable 是典型的函数式接口,支持 Lambda 表达式


为什么优先 Runnable

Java 不支持多重继承。如果一个类已经继承了其他父类(如 JFrameException 等),就无法再继承 Thread 类。而实现 Runnable 接口则没有此限制,同时还能保持良好的面向对象设计:

  • 职责分离:任务逻辑与线程控制分离

  • 更高的灵活性和复用性

  • 符合“组合优于继承”的设计原则


基础用法

步骤 1:定义任务类

创建一个类实现 Runnable 接口,并重写 run() 方法:

class DataProcessor implements Runnable {
    @Override
    public void run() {
        System.out.println("数据处理任务正在运行...");
        // 模拟耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("数据处理完成");
    }
}

步骤 2:启动线程

Runnable 实例传给 Thread 构造器,调用 start() 启动新线程:

public class MainApp {
    public static void main(String[] args) {
        DataProcessor task = new DataProcessor();
        Thread worker = new Thread(task);
        worker.start(); // 启动新线程执行 run()
    }
}

调用 start() 会创建新线程并自动调用 run();若直接调用 task.run(),则仍在主线程中同步执行,不会开启新线程


Lambda 简化代码

由于 Runnable 是函数式接口,可用 Lambda 表达式替代匿名内部类或独立类:

public class LambdaDemo {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("使用 Lambda 执行的线程任务");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Lambda 任务结束");
        };

        new Thread(task).start();
    }
}

输出:

使用 Lambda 执行的线程任务
Lambda 任务结束

这种写法简洁、直观,特别适合一次性、轻量级任务。


结合 Executor 管理线程池

手动创建 Thread 对象效率低且难以管理。Java 提供了 Executor 框架用于线程池管理:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo {
    public static void main(String[] args) {
        // 创建固定大小为 2 的线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);

        // 提交多个任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("任务 " + taskId + " 由线程 " 
                    + Thread.currentThread().getName() + " 执行");
            });
        }

        // 关闭线程池(不再接受新任务,但会执行完已提交任务)
        executor.shutdown();
    }
}

优势:

  • 自动复用线程,减少创建 / 销毁开销

  • 控制并发数量,防止资源耗尽

  • 更高级的调度策略(如定时、周期性任务)


异常处理

run() 方法不能抛出检查型异常(因其签名未声明 throws),所有异常必须在方法内部处理:

class RiskyTask implements Runnable {
    @Override
    public void run() {
        try {
            // 可能抛出运行时异常的操作
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.err.println("捕获到算术异常: " + e.getMessage());
            // 记录日志、回滚状态等
        } catch (Exception e) {
            System.err.println("未知异常: " + e.getMessage());
        }
        // 若不捕获,线程会静默终止!
    }
}

public class ExceptionDemo {
    public static void main(String[] args) {
        new Thread(new RiskyTask()).start();
    }
}

未捕获的异常会导致线程直接终止,且不会传播到主线程,容易造成“静默失败”。


Runnable vs Thread

对比维度

Runnable 接口

Thread

类型

接口,定义任务

类,表示线程本身

继承限制

可同时继承其他类

无法再继承其他类(Java 单继承)

设计原则

任务与执行解耦,高内聚低耦合

任务与线程耦合,灵活性差

代码复用性

高(同一任务可被多个线程执行)

低(每个线程需单独实例化)

内存与性能

更轻量,线程由外部创建

每个实例都携带完整线程开销

推荐程度

✅ 官方推荐方式

❌ 仅适用于简单场景


重点总结

  • Runnable 是 Java 多线程编程的标准任务模型

  • 必须通过 ThreadExecutor 来执行 Runnable 任务

  • run() 方法不能抛出检查型异常,需内部处理所有异常

  • Lambda 表达式可极大简化 Runnable 的编写

  • 在生产环境中应优先使用 ExecutorService 管理线程,而非手动创建 Thread

  • 实现 Runnable 比继承 Thread 更符合面向对象设计原则


思考题

  1. 如果一个类已经继承了某个父类,是否还能使用多线程?如何实现?

  2. 为什么 run() 方法不能声明抛出检查型异常?这对程序设计有何影响?

  3. 在 Web 应用中,为什么通常不建议直接使用 new Thread().start(),而要使用线程池?