第七次博客作业:继承 Thread vs 实现 Runnable,多线程创建的抉择

前言

今天的课堂作业主题是 “使用 Thread 子类创建线程 VS 使用 Runnable 接口”。这是每一个 Java 开发者在入门多线程时都会遇到的经典问题。虽然现在的开发中我们更多使用线程池或  CompletableFuture ,但理解这两种基础方式的区别,是掌握并发编程底层逻辑的基石。

今天这篇随笔,我将结合代码示例和设计原则,聊聊为什么在很多场景下,我们更推荐使用  Runnable  而不是直接继承  Thread 。

  1. 两种方式初印象

首先,让我们回顾一下这两种最基础的创建线程的方式。

方式一:继承 Thread 类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我是通过继承 Thread 创建的线程");
}
}

// 启动方式
new MyThread().start();
这种方式非常直观,就像我们在定义一个普通的类一样,重写  run()  方法即可。

方式二:实现 Runnable 接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我是通过实现 Runnable 创建的线程");
}
}

// 启动方式
new Thread(new MyRunnable()).start();
这种方式稍微绕一点,需要把实现了  Runnable  的对象作为参数传递给  Thread  的构造器。

  1. 核心区别深度解析

既然都能创建线程,为什么教材和老程序员总是强调“优先使用 Runnable”?主要基于以下三个维度的考量:

2.1 避免单继承的局限性(最重要)

Java 语言的一个核心特性是 单继承(Single Inheritance)。

如果你选择了继承  Thread  类,那么你的类就失去了继承其他类的机会。如果你的业务逻辑本身就需要继承某个父类(比如  BaseService ),这时候会就陷入死胡同。

而实现  Runnable  接口则完全没有这个问题。你的类依然可以继承其他类,同时实现多个接口,保持了设计的灵活性。

2.2 逻辑与控制的解耦

从面向对象设计的原则来看,职责分离非常重要。

Thread 类:它的职责是管理线程的生命周期(如  start ,  sleep ,  join ,  interrupt  等),它是线程的“控制者”。

Runnable 接口:它的职责仅仅是定义“任务”,即线程要执行的代码逻辑。

当我们使用  Runnable  时,我们将“做什么”(任务逻辑)和“怎么做”(线程调度)分离开了。这意味着同一个  Runnable  任务,既可以由  Thread  执行,也可以交给线程池( ExecutorService )执行,甚至可以在当前线程直接调用  run()  方法(虽然这就不是异步了),代码的复用性大大增强。

2.3 资源共享的便利性

假设我们要模拟“卖票”场景,有 100 张票,3 个窗口同时卖。

使用 Thread:如果每个窗口是一个  Thread  子类对象,那么票数变量如果是成员变量,每个对象各有一份,互不干扰,很难实现共享(除非用 static,但这又引入了全局变量的风险)。

使用 Runnable:我们可以只创建一个  Runnable  实例(包含 100 张票的数据),然后把这个同一个实例传给三个  Thread  对象。这样,三个线程天然共享这份数据,处理同步问题更加自然。
结论:

虽然在语法上两者都能工作,但在实际工程实践中,实现  Runnable  接口(以及后来的  Callable  接口)通常是更好的选择。它不仅规避了 Java 单继承的限制,还符合“面向接口编程”和“高内聚低耦合”的设计思想。

posted @ 2026-06-10 22:19  定缘  阅读(4)  评论(0)    收藏  举报