目录
- 为什么需要
Runnable? - 解决 Java 单继承的局限性 Runnable是什么? - 接口定义与核心方法- 如何使用
Runnable创建并启动线程? - 标准三步法 Runnablevs.Thread类 - 核心区别与优势- 实践案例:一个简单的多线程示例
Runnable的实际应用:线程池Runnable与Callable的区别
为什么需要 Runnable?
在 Java 中,创建线程有两种主要方式:

- 继承
Thread类 - 实现
Runnable接口
如果只使用继承 Thread 类,会带来一个问题:Java 不支持多继承,如果一个类已经继承了一个父类(class MyData extends SomeOtherClass),它就不能再继承 Thread 类了,而实现 Runnable 接口则没有这个限制,因为一个类可以实现多个接口。
Runnable 提供了一种更灵活、更符合面向对象设计原则的方式来创建多线程任务。
Runnable 是什么?
Runnable 是 Java 中一个非常简单的函数式接口(Functional Interface),它位于 java.lang 包中。
@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();
}
核心要点:
- 只有一个抽象方法:
public void run()。 - 作用:
run()方法包含了线程要执行的代码逻辑,当线程启动后,JVM 会自动调用该线程关联的Runnable对象的run()方法。 - 注意:
run()方法不会自动启动新线程,它只是一个普通的方法,直接调用myRunnable.run()会在当前线程中顺序执行,而不会创建新线程,必须通过Thread对象来启动它。
如何使用 Runnable 创建并启动线程?(标准三步法)
这是使用 Runnable 的标准流程:
第一步:创建一个 Runnable 接口的实现类
创建一个类,让它实现 Runnable 接口,并重写 run() 方法。
// 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 {
// 模拟任务执行,休眠 500 毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(taskName + " has finished.");
}
}
第二步:创建 Thread 对象,并将 Runnable 实例作为参数传入
Thread 类的构造函数可以接收一个 Runnable 对象,当你将一个 Runnable 传递给 Thread 时,这个 Runnable 就成为了该线程的“任务”。
// 2. 创建 Runnable 实例
MyTask task1 = new MyTask("Task-A");
MyTask task2 = new MyTask("Task-B");
// 3. 创建 Thread 对象,并将 Runnable 实例传入
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
第三步:调用 start() 方法启动线程
调用 thread.start() 是最关键的一步,这会做两件事:
- 创建一个新的线程。
- 在新线程中调用
Runnable对象的run()方法。
千万不要调用 run() 方法,那只是在主线程中顺序执行任务,失去了多线程的意义。
// 4. 启动线程 thread1.start(); thread2.start();
完整示例代码:
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);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(taskName + " has finished.");
}
}
public class RunnableDemo {
public static void main(String[] args) {
System.out.println("Main thread starts.");
// 1. 创建 Runnable 实例
MyTask task1 = new MyTask("Task-A");
MyTask task2 = new MyTask("Task-B");
// 2. 创建 Thread 对象
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
// 3. 启动线程
thread1.start();
thread2.start();
System.out.println("Main thread ends.");
}
}
可能的输出(因为线程执行顺序不确定,所以每次运行结果可能不同):
Main thread starts.
Main thread ends.
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 thread ends. 可能会在两个任务执行完之前就打印出来,这恰恰体现了多线程的并发特性。
Runnable vs. Thread 类
| 特性 | 实现 Runnable 接口 |
继承 Thread 类 |
|---|---|---|
| 继承性 | 灵活,一个类可以实现多个接口,同时还可以继承一个类。 | 受限,Java 不支持多继承,所以不能再继承其他类。 |
| 资源共享 | 简单、自然,多个 Thread 实例可以共享同一个 Runnable 对象,从而共享其数据。 |
复杂,如果多个线程需要共享数据,需要将数据定义为 static 或通过构造函数传递,容易出错。 |
| 设计模式 | 推荐,符合“任务”与“执行者”分离的原则。Runnable 是任务,Thread 是执行者。 |
不推荐,将任务逻辑和线程管理逻辑耦合在同一个类中,不符合单一职责原则。 |
| 启动方式 | 必须创建 Thread 实例,并传入 Runnable,然后调用 start()。 |
直接创建 Thread 子类的实例,然后调用 start()。 |
在绝大多数情况下,优先选择实现 Runnable 接口的方式,它更灵活、更安全,也更符合现代 Java 编程思想。
实践案例:一个简单的多线程示例
假设我们要模拟一个售票系统,有多个窗口同时售票。
// 票务中心,票是共享资源
class TicketOffice {
private int totalTickets = 10;
public void sellTickets(String windowName) {
while (totalTickets > 0) {
// 使用同步块来防止线程安全问题
synchronized (this) {
if (totalTickets > 0) {
System.out.println(windowName + " is selling ticket No." + totalTickets);
totalTickets--;
try {
Thread.sleep(100); // 模拟售票耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
System.out.println(windowName + " has no more tickets to sell.");
}
}
// 售票窗口任务
class TicketSeller implements Runnable {
private TicketOffice office;
private String windowName;
public TicketSeller(TicketOffice office, String name) {
this.office = office;
this.windowName = name;
}
@Override
public void run() {
office.sellTickets(windowName);
}
}
public class TicketSellingDemo {
public static void main(String[] args) {
TicketOffice office = new TicketOffice();
// 创建三个售票窗口
Thread window1 = new Thread(new TicketSeller(office, "Window-1"));
Thread window2 = new Thread(new TicketSeller(office, "Window-2"));
Thread window3 = new Thread(new TicketSeller(office, "Window-3"));
window1.start();
window2.start();
window3.start();
}
}
这个例子中,TicketSeller 实现了 Runnable,它持有一个共享的 TicketOffice 引用,多个 Thread 对象共享同一个 TicketSeller 实例(或者多个实例但共享同一个 TicketOffice),从而实现了对共享资源的访问。
Runnable 的实际应用:线程池
在现代 Java 应用中,我们通常不会直接创建和销毁线程,因为这是一个昂贵的操作,而是使用线程池。
Runnable 是线程池中最核心的任务单元,当你向线程池提交一个任务时,你提交的就是一个 Runnable 对象。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小为 3 的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交 10 个任务到线程池
for (int i = 1; i <= 10; i++) {
final int taskId = i;
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Task-" + taskId + " is being executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task-" + taskId + " finished.");
}
};
executor.execute(task); // 提交任务
}
// 关闭线程池
executor.shutdown();
}
}
在这个例子中,我们创建了一个 Runnable 匿名类来定义任务,然后通过 executor.execute(task) 将其提交给线程池执行,线程池会从其内部的线程队列中取出一个空闲线程来执行这个 Runnable 的 run() 方法。
Runnable 与 Callable 的区别
Runnable 有一个重要的局限性:它的 run() 方法不能返回结果,也不能抛出受检异常,为了解决这个问题,Java 引入了 Callable 接口。
| 特性 | Runnable |
Callable |
|---|---|---|
| 核心方法 | void run() |
V call() throws Exception |
| 返回值 | 无 | 有 (泛型 V) |
| 异常 | 不能抛出受检异常 | 可以抛出受检异常 |
| 如何获取结果 | 无法直接获取 | 需要通过 Future<V> 对象来获取 |
| 使用场景 | 适用于不需要返回结果的并发任务 | 适用于需要返回结果或可能抛出异常的并发任务 |
Callable 是 Runnable 的增强版。
Runnable是 Java 多线程编程的基础,它定义了一个可以被线程执行的任务。- 使用
Runnable的标准流程:创建实现类 -> 创建Thread对象 -> 调用start()。 - 核心优势:解决了 Java 单继承的局限,实现了任务与线程的分离,使得资源共享更简单,设计更灵活。
- 与
Thread类相比,Runnable是更推荐的方式。 - 在现代 Java 中,
Runnable是线程池(ExecutorService)的标准任务单元,是实现高并发应用的关键部分。
掌握 Runnable 是迈向高级 Java 并发编程的第一步,也是最重要的一步。
