杰瑞科技汇

Java多线程中Runnable如何正确使用?

什么是 Runnable

Runnable 是 Java 中一个非常重要的函数式接口,它位于 java.lang 包中,它只定义了一个抽象方法 run()

Java多线程中Runnable如何正确使用?-图1
(图片来源网络,侵删)
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

核心作用: Runnable 接口为 Java 提供了一种“将任务与执行线程解耦”的机制,你可以把它想象成一个“任务说明书”或者“待办事项列表”。

  • 任务: 你需要执行的代码逻辑,run() 方法里的内容。
  • 执行者: 真正执行这个任务的线程。

Runnable 本身并不能运行,它只是一个可以被“执行”的任务描述,你需要将它交给一个 Thread 对象,由这个 Thread 对象去调用 run() 方法,从而在新的线程中执行任务。


为什么要使用 Runnable?(而不是继承 Thread

在 Java 中实现多线程主要有两种方式:

  1. 继承 java.lang.Thread 类。
  2. 实现 java.lang.Runnable 接口。

通常情况下,更推荐使用 Runnable 接口,原因如下:

Java多线程中Runnable如何正确使用?-图2
(图片来源网络,侵删)
特性 继承 Thread 实现 Runnable 接口
类继承限制 Java 是单继承的,一个类继承了 Thread 类就无法再继承其他父类。 灵活,一个类可以实现 Runnable 接口,同时还可以继承其他类或实现其他接口。
资源共享 不方便,如果多个线程需要操作同一份共享数据,继承 Thread 会让数据和线程强耦合。 非常方便,多个 Thread 对象可以共享同一个 Runnable 实例,从而轻松实现资源共享。
设计模式 违反了“组合优于继承”的设计原则。 符合“面向接口编程”和“组合”的设计思想,代码结构更清晰、更灵活。

Runnable 更符合 Java 的设计哲学,提供了更好的灵活性和代码复用性,尤其是在需要多个线程处理同一份数据的场景下。


如何使用 Runnable?(标准三步法)

使用 Runnable 创建并启动一个线程非常简单,遵循以下三个步骤:

步骤 1:创建一个 Runnable 任务类

创建一个类,实现 Runnable 接口,并重写 run() 方法,在 run() 方法中编写你希望在新线程中执行的代码。

// 步骤 1: 定义一个任务类,实现 Runnable 接口
class MyTask implements Runnable {
    private String taskName;
    public MyTask(String taskName) {
        this.taskName = taskName;
    }
    @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) {
                e.printStackTrace();
            }
        }
        System.out.println(taskName + " has finished.");
    }
}

步骤 2:创建 Thread 对象,并将 Runnable 实例作为参数传入

Thread 类的构造函数可以接收一个 Runnable 对象。

// 步骤 2: 创建 Thread 对象,并将 Runnable 实例传递给它
MyTask task1 = new MyTask("Task-A");
Thread thread1 = new Thread(task1);

步骤 3:调用 start() 方法启动线程

非常重要的一点: 必须调用 thread.start() 方法,而不是 thread.run()

  • thread.start(): 会创建一个新的线程,并让这个新线程去调用 run() 方法。这才是真正的多线程。
  • thread.run(): 只是在当前线程中直接调用 run() 方法,不会创建新线程,代码会变成单线程顺序执行。
// 步骤 3: 调用 start() 方法启动线程
thread1.start();

完整代码示例

下面是一个完整的、可运行的示例,演示了如何使用 Runnable 启动两个线程。

// MyTask.java
class MyTask implements Runnable {
    private String taskName;
    public MyTask(String taskName) {
        this.taskName = taskName;
    }
    @Override
    public void run() {
        // 获取当前执行的线程对象
        Thread currentThread = Thread.currentThread();
        System.out.println(taskName + " is running in thread: " + currentThread.getName());
        for (int i = 1; i <= 5; i++) {
            System.out.println(taskName + " count: " + i);
            try {
                Thread.sleep(500); // 休眠 500 毫秒,让线程调度有机会发生
            } catch (InterruptedException e) {
                System.out.println(taskName + " was interrupted.");
                e.printStackTrace();
            }
        }
        System.out.println(taskName + " has finished.");
    }
}
// Main.java
public class RunnableDemo {
    public static void main(String[] args) {
        System.out.println("Main thread starts: " + Thread.currentThread().getName());
        // 步骤 1 & 2: 创建任务和线程
        MyTask task1 = new MyTask("Task Alpha");
        MyTask task2 = new MyTask("Task Beta");
        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);
        // 步骤 3: 启动线程
        thread1.start();
        thread2.start();
        System.out.println("Main thread has finished starting the tasks.");
    }
}

可能的输出结果(线程执行顺序不固定):

Main thread starts: main
Main thread has finished starting the tasks.
Task Alpha is running in thread: Thread-0
Task Alpha count: 1
Task Beta is running in thread: Thread-1
Task Beta count: 1
Task Alpha count: 2
Task Beta count: 2
Task Alpha count: 3
Task Beta count: 3
Task Alpha count: 4
Task Beta count: 4
Task Alpha count: 5
Task Alpha has finished.
Task Beta count: 5
Task Beta has finished.

从输出可以看到,main 线程、Thread-0Thread-1 三个线程在并发执行,它们的打印顺序是交错的,这证明了多线程的特性。


进阶:使用 Lambda 表达式(Java 8+)

由于 Runnable 是一个函数式接口(只有一个抽象方法),我们可以使用 Java 8 引入的 Lambda 表达式来简化代码,无需创建一个单独的 MyTask 类。

public class RunnableLambdaDemo {
    public static void main(String[] args) {
        System.out.println("Main thread starts: " + Thread.currentThread().getName());
        // 使用 Lambda 表达式直接创建 Runnable 任务
        Thread thread1 = new Thread(() -> {
            System.out.println("Lambda Task 1 is running in thread: " + Thread.currentThread().getName());
            for (int i = 1; i <= 3; i++) {
                System.out.println("Lambda Task 1 count: " + i);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Lambda Task 1 has finished.");
        });
        // 也可以给一个 Runnable 实例命名
        Runnable task2 = () -> {
            System.out.println("Lambda Task 2 is running in thread: " + Thread.currentThread().getName());
            System.out.println("Lambda Task 2 is simple!");
        };
        thread1.start();
        new Thread(task2, "Lambda-Thread-2").start(); // 启动线程并指定线程名
        System.out.println("Main thread has finished starting the tasks.");
    }
}

这种方式代码更简洁,特别适合于那些只需要简单执行一段逻辑的“一次性”任务。


RunnableFutureExecutorService 的结合(现代方式)

在实际的企业级应用中,我们通常不直接手动创建和启动 Thread 对象,而是使用 ExecutorService(线程池)来管理线程。Runnable 可以完美地与线程池配合使用。

ExecutorServicesubmit() 方法可以接收一个 Runnable 任务,并返回一个 Future 对象,虽然 Runnable 任务本身不返回结果,但 Future 可以用来跟踪任务的状态(如是否完成、是否被取消)。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorServiceRunnableDemo {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
        System.out.println("Submitting tasks to the executor...");
        // 提交一个 Runnable 任务
        Runnable task1 = () -> {
            System.out.println("Executor Task is running in: " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Executor Task has finished.");
        };
        // submit() 方法会立即返回,并返回一个 Future 对象
        Future<?> future = executor.submit(task1);
        // 可以检查任务是否完成
        System.out.println("Task submitted. Is it done? " + future.isDone());
        // 关闭线程池(非常重要!)
        // shutdown() 会停止接受新任务,但会执行完队列中已有的任务
        executor.shutdown();
        System.out.println("Main thread continues...");
    }
}

优点:

  • 资源复用: 线程池避免了频繁创建和销毁线程的开销。
  • 任务管理: 可以方便地管理任务队列、线程数量等。
  • 生命周期管理: 提供了优雅的关闭机制。

特性 描述
核心概念 Runnable 是一个任务,Thread 是执行者。
主要优点 灵活性高:避免了单继承限制。
资源共享:多个线程可共享同一个 Runnable 实例。
设计优雅:符合组合优于继承的原则。
基本用法 创建实现 Runnable 的类 -> 2. 创建 Thread 对象并传入 Runnable -> 3. 调用 thread.start()
现代实践 ExecutorService (线程池) 和 Future 结合使用,是生产环境中管理多线程任务的标准方式。
简化写法 使用 Lambda 表达式可以极大简化代码,无需显式定义实现类。

掌握 Runnable 是理解 Java 多线程编程的基石,它为你构建复杂、高效、可维护的并发应用程序打下了坚实的基础。

分享:
扫描分享到社交APP
上一篇
下一篇