什么是 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 创建线程的标准步骤如下:
-
创建一个
Runnable任务:- 实现
Runnable接口 - 使用 Lambda 表达式(推荐)
- 实现
-
创建
Thread对象:将Runnable实例作为参数传递给Thread的构造函数。 -
调用
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-A、Task-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 类。
高级用法:Runnable 与 ExecutorService (线程池)
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)的基础组件,是实现高效并发编程的关键。
