杰瑞科技汇

Java Runnable接口如何实现多线程?

什么是 Runnable 接口?

Runnable 是 Java 中一个函数式接口,它代表一个可以被线程执行的任务,它只有一个抽象方法 run()

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

核心要点:

  • 任务与线程分离Runnable 的核心思想是将“要执行的任务”(run 方法的代码)和“执行任务的机制”(Thread 类)分离开来,这使得你可以在不同的线程中执行同一个任务,或者将同一个任务提交给不同的执行框架(如线程池)。
  • 无返回值Runnablerun 方法没有返回值 (void),这意味着如果你通过 Runnable 执行一个任务,并且该任务需要计算一个结果,你无法直接通过 run 方法返回这个结果,如果需要返回结果,应该使用 Callable 接口。
  • 不处理检查型异常run 方法不能抛出检查型异常(checked exception),IOExceptionrun 方法内部可能会抛出检查型异常,你必须在方法内部使用 try-catch 块来处理它,这是它与普通方法的一个重要区别。

如何使用 Runnable 接口?

使用 Runnable 通常遵循以下三个步骤:

  1. 创建任务类:定义一个类,实现 Runnable 接口,并重写 run() 方法,将你要执行的任务代码放入 run() 方法中。
  2. 创建 Runnable 实例:创建上面定义的任务类的对象。
  3. 创建线程并启动:将 Runnable 实例作为参数传递给 Thread 类的构造函数,创建一个 Thread 对象,然后调用该对象的 start() 方法来启动线程。

示例代码

这是一个最经典的 "Hello World" 并发示例:

// 1. 创建任务类:实现 Runnable 接口
class MyTask implements Runnable {
    private String taskName;
    public MyTask(String name) {
        this.taskName = name;
    }
    @Override
    public void run() {
        // 任务要执行的代码
        for (int i = 1; i <= 5; i++) {
            System.out.println(taskName + " is running, count: " + i);
            try {
                // 模拟任务耗时
                Thread.sleep(500); // 休眠 500 毫秒
            } catch (InterruptedException e) {
                // 处理中断异常
                System.out.println(taskName + " was interrupted.");
                // 恢复中断状态(良好实践)
                Thread.currentThread().interrupt();
                return; // 中断后退出任务
            }
        }
        System.out.println(taskName + " has finished.");
    }
}
// 主程序
public class RunnableExample {
    public static void main(String[] args) {
        // 2. 创建 Runnable 实例
        MyTask task1 = new MyTask("Task-A");
        MyTask task2 = new MyTask("Task-B");
        // 3. 创建线程并启动
        Thread thread1 = new Thread(task1, "Thread-1");
        Thread thread2 = new Thread(task2, "Thread-2");
        System.out.println("Starting threads...");
        thread1.start();
        thread2.start();
        System.out.println("Main thread continues its work...");
    }
}

可能的输出结果(因为线程执行顺序不确定,所以每次运行结果可能不同):

Starting threads...
Main thread continues its work...
Task-A is running, count: 1
Task-B is running, count: 1
Task-A is running, count: 2
Task-B is running, count: 2
Task-A is running, count: 3
Task-B is running, count: 3
Task-A is running, count: 4
Task-B is running, count: 4
Task-A is running, count: 5
Task-B is running, count: 5
Task-A has finished.
Task-B has finished.

从输出可以看出,Task-ATask-B 的代码是交替执行的,这证明了它们在不同的线程中并发运行,主线程 main 在启动了这两个线程后,也继续执行自己的代码,并没有阻塞。


使用 Lambda 表达式(Java 8+)

由于 Runnable 是一个函数式接口(只有一个抽象方法),我们可以使用更简洁的 Lambda 表达式来创建任务实例,而无需定义一个实现类。

上面的示例可以改写为:

public class RunnableLambdaExample {
    public static void main(String[] args) {
        // 使用 Lambda 表达式直接创建 Runnable 实例
        Runnable task1 = () -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Lambda-Task-A is running, count: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Lambda-Task-A has finished.");
        };
        // 同样可以创建另一个
        Runnable task2 = () -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Lambda-Task-B is running, count: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Lambda-Task-B has finished.");
        };
        // 创建并启动线程
        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);
        thread1.start();
        thread2.start();
    }
}

这种方式代码更紧凑,是现代 Java 开发中推荐的做法。


Runnable vs. 继承 Thread

在 Java 中,除了实现 Runnable 接口,还可以通过继承 Thread 类来创建线程,这两种方式各有优缺点。

特性 实现 Runnable 接口 继承 Thread
核心思想 任务与执行分离,更灵活。 任务与执行绑定,耦合度高。
继承限制 没有限制,一个类可以实现多个接口,但不能继承多个类。 有限制,Java 是单继承的,继承了 Thread 类就不能再继承其他父类了。
资源共享 非常方便,多个 Thread 对象可以共享同一个 Runnable 实例,从而共享其实例变量。 不方便,每个 Thread 对象都有自己的副本,共享数据需要额外的同步机制。
代码复用 Runnable 实例可以被多个线程使用,也可以提交给线程池。 Thread 子类本身就是一个线程,复用性较差。
面向对象 更符合 OOP 设计原则,将“行为”(run 方法)和“实体”(Thread)分开。 耦合度高,将行为和实体混在一起。
推荐度 强烈推荐,这是实现多线程的标准和首选方式。 不推荐,除非有非常特殊的需求(需要重写 Thread 类的其他方法)。

在绝大多数情况下,都应该优先选择实现 Runnable 接口的方式来创建多线程。


Runnable 与线程池

Runnable 接口的重要性在现代 Java 并发编程中更加凸显,因为它与 Executor 框架(线程池)完美集成。

你不能直接将一个 Thread 对象提交给线程池,但你可以将一个 Runnable 任务提交给线程池,由池中的某个工作线程来执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunnableWithThreadPool {
    public static void main(String[] args) {
        // 创建一个固定大小为 2 的线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
        // 提交多个 Runnable 任务给线程池
        executor.submit(() -> {
            System.out.println("Task 1 is running in pool.");
        });
        executor.submit(() -> {
            System.out.println("Task 2 is running in pool.");
        });
        executor.submit(() -> {
            System.out.println("Task 3 is running in pool.");
        });
        // 关闭线程池
        executor.shutdown(); // 等待所有任务完成后再关闭
        // executor.shutdownNow(); // 立即停止所有正在执行的任务
    }
}

在这个例子中,我们创建了 3 个 Runnable 任务,但线程池大小只有 2,线程池会管理这些任务的执行,无需我们手动创建和销毁 Thread 对象,大大提高了资源利用率和程序性能。


  • 定义Runnable 是一个任务接口,其 run() 方法定义了任务要执行的代码。
  • 核心优势解耦,它将任务逻辑与线程机制分离,使得代码更灵活、更易于复用和共享。
  • 使用方式:创建 Runnable 实例 -> 创建 Thread 对象(或提交给线程池)-> 启动。
  • 现代实践:使用 Lambda 表达式 来创建 Runnable 实例,代码更简洁。
  • 最佳实践优先选择实现 Runnable 接口,而不是继承 Thread 类。
  • 重要性与未来RunnableExecutor 框架的基础,是理解和使用现代 Java 并发编程(如 CompletableFutureStream 并行处理)的基石,虽然 FutureCompletableFuture 提供了更强大的异步编程能力,但 Runnable 依然是定义简单、无返回值任务的基石。
分享:
扫描分享到社交APP
上一篇
下一篇