杰瑞科技汇

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

什么是 Runnable

Runnable 是 Java 中一个函数式接口(Functional Interface),它位于 java.lang 包中,它的定义非常简单:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
  • 核心作用Runnable 接口定义了一个任务,这个任务可以被一个线程执行。
  • 核心方法void run(),这个方法包含了线程要执行的代码逻辑。
  • @FunctionalInterface 注解:表明它是一个函数式接口,意味着可以用 Lambda 表达式来简化创建实例的过程。

Runnable 就是一个“任务说明书”,它告诉线程“你应该做什么”,但它本身不是线程


为什么需要 Runnable

Java 不支持多重继承,如果一个类已经继承了某个父类(UI 类),它就不能再继承 Thread 类来创建线程了,而实现 Runnable 接口则可以避免这个问题,因为它只是实现一个接口,不影响类的继承体系。

更重要的是,Runnable 更符合“任务”与“执行者”分离的设计思想:

  • 任务Runnable 对象,只包含要执行的代码。
  • 执行者Thread 对象,负责执行 Runnable 中的任务。

这种设计更加灵活,一个任务可以被多个线程执行,或者一个线程可以执行多个任务(通过实现 ExecutorService)。


如何使用 Runnable 创建并启动线程?

使用 Runnable 创建线程的标准步骤如下:

  1. 创建一个 Runnable 任务

    • 实现 Runnable 接口
    • 使用 Lambda 表达式(推荐)
  2. 创建 Thread 对象:将 Runnable 实例作为参数传递给 Thread 的构造函数。

  3. 调用 start() 方法:调用 thread.start() 来启动线程。注意:是 start(),不是 run()

    • thread.start():会启动一个新的线程,并让 JVM 在该线程中调用 thread.run() 方法。
    • thread.run()不会创建新线程,它只是在当前线程中执行 run() 方法的代码,效果等同于普通的方法调用。

代码示例

示例 1:传统方式(实现 Runnable 接口)

// 1. 定义一个任务类,实现 Runnable 接口
class MyTask implements Runnable {
    private String taskName;
    public MyTask(String name) {
        this.taskName = name;
    }
    // 2. 实现 run() 方法,这是线程要执行的核心逻辑
    @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.");
    }
}
public class RunnableDemo {
    public static void main(String[] args) {
        // 3. 创建 Runnable 任务实例
        MyTask task1 = new MyTask("Task-A");
        MyTask task2 = new MyTask("Task-B");
        // 4. 创建 Thread 对象,并将任务实例传递给它
        Thread thread1 = new Thread(task1);
        Thread thread2 = new Thread(task2);
        // 5. 启动线程
        thread1.start();
        thread2.start();
        System.out.println("Main thread has finished.");
    }
}

可能的输出结果(顺序可能不同):

Main thread has finished.
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 has finished.
Task-B has finished.

可以看到,main 线程和 Task-ATask-B 线程是并发执行的,它们的输出是交替出现的。


示例 2:现代方式(使用 Lambda 表达式)

从 Java 8 开始,我们可以使用 Lambda 表达式来更简洁地创建 Runnable 实例,这大大减少了样板代码。

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

这个例子和上一个的功能完全相同,但代码更简洁、更具可读性。


Runnable vs. Thread(继承方式)

特性 实现 Runnable 接口 继承 Thread
类继承 不受限制,可以继承其他类 受限,Java 不支持多重继承
资源共享 非常适合,多个线程可以共享同一个 Runnable 实例,从而共享其数据。 不方便,每个线程对象是独立的,共享数据需要额外处理。
设计模式 推荐,符合“任务与执行者分离”的原则,设计更灵活。 不推荐,紧耦合,扩展性差。
代码简洁性 配合 Lambda 表达式非常简洁。 需要创建一个子类,代码量稍多。

在实际开发中,强烈推荐使用 Runnable 接口(特别是 Lambda 表达式) 来创建线程,而不是继承 Thread 类。


高级用法:RunnableExecutorService (线程池)

Runnable 的真正威力在与线程池(ExecutorService)结合时才能完全发挥,直接创建和销毁线程是非常消耗资源的,线程池可以复用已存在的线程,从而提高性能。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolRunnableDemo {
    public static void main(String[] args) {
        // 创建一个固定大小为2的线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
        // 提交10个任务到线程池
        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            Runnable task = () -> {
                System.out.println("Task-" + taskId + " is being executed by thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Task-" + taskId + " has finished.");
            };
            executor.execute(task); // 提交任务
        }
        // 关闭线程池
        executor.shutdown(); // 禁止提交新任务,但已提交的任务会继续执行
        try {
            // 等待所有任务完成,最多等待60秒
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow(); // 如果超时,则强制关闭
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }
        System.out.println("All tasks are done. Main thread finished.");
    }
}

在这个例子中,我们创建了10个 Runnable 任务,但只用了2个线程来执行它们,线程池会管理这些线程的生命周期,大大提高了效率。

  • Runnable 是一个任务,而不是线程本身。
  • 它定义了 run() 方法,包含线程要执行的代码。
  • 通过创建 Thread 对象并传入 Runnable 实例,然后调用 start() 来启动新线程。
  • 使用 Runnable(特别是 Lambda 表达式)比继承 Thread 类更灵活、更符合现代设计原则。
  • Runnable 是 Java 线程池(ExecutorService)的基础组件,是实现高效并发编程的关键。
分享:
扫描分享到社交APP
上一篇
下一篇