Java作业:两种创建线程方式的对比
方式核心类/接口实现方法方式一继承Thread类重写run()方法方式二实现Runnable接口实现run()方法,传入Thread对象这两种方式都可以创建并启动线程,但在设计理念、使用场景和灵活性上存在显著差异。核心问题:我们应该在什么时候选择继承Thread类?什么时候选择实现Runnable接口?线程(Thread)是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单
|知识点:Thread类、Runnable接口、线程创建、多线程编程
一、作业概述
在Java中,创建线程主要有两种方式:
| 方式 | 核心类/接口 | 实现方法 |
|---|---|---|
| 方式一 | 继承Thread类 | 重写run()方法 |
| 方式二 | 实现Runnable接口 | 实现run()方法,传入Thread对象 |
这两种方式都可以创建并启动线程,但在设计理念、使用场景和灵活性上存在显著差异。
核心问题:我们应该在什么时候选择继承Thread类?什么时候选择实现Runnable接口?
二、基础知识回顾
2.1 什么是线程?
线程(Thread)是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。
2.2 Java中的线程模型
text
Java线程模型
├── Thread类(java.lang.Thread)
│ └── 代表一个线程对象
└── Runnable接口(java.lang.Runnable)
└── 定义可执行的任务代码
2.3 线程的生命周期
text
新建(New) → 就绪(Runnable) → 运行(Running) → 阻塞(Blocked) → 终止(Terminated)
↑ ↓
└── 时间片用完 ────┘
三、方式一:继承Thread类
3.1 实现步骤
-
定义一个类继承
Thread类 -
重写
run()方法,将线程要执行的任务代码放入其中 -
创建该类的实例(即创建线程对象)
-
调用线程对象的
start()方法启动线程
3.2 代码实现
/**
* 方式一:继承Thread类创建线程
*/
public class ThreadDemo {
public static void main(String[] args) {
// 3. 创建线程对象
MyThread t1 = new MyThread("线程A");
MyThread t2 = new MyThread("线程B");
// 4. 启动线程(会自动调用run方法)
t1.start();
t2.start();
// 主线程也执行任务
for (int i = 0; i < 5; i++) {
System.out.println("主线程正在执行:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 1. 继承Thread类
* 2. 重写run()方法
*/
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "正在执行:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.3 运行结果(示例)
text
线程A正在执行:0 主线程正在执行:0 线程B正在执行:0 线程A正在执行:1 主线程正在执行:1 线程B正在执行:1 线程A正在执行:2 主线程正在执行:2 ...
3.4 继承Thread类的特点
| 特点 | 说明 |
|---|---|
| 简单直接 | 代码结构简单,易于理解 |
| 线程独立 | 每个线程对象都是独立的Thread实例 |
| 单继承限制 | Java不支持多继承,继承Thread后无法再继承其他类 |
四、方式二:实现Runnable接口
4.1 实现步骤
-
定义一个类实现
Runnable接口 -
实现
run()方法,将线程要执行的任务代码放入其中 -
创建该类的实例(即任务对象)
-
创建
Thread对象,将任务对象作为参数传入 -
调用
Thread对象的start()方法启动线程
4.2 代码实现
/**
* 方式二:实现Runnable接口创建线程
*/
public class RunnableDemo {
public static void main(String[] args) {
// 3. 创建任务对象
MyRunnable task1 = new MyRunnable("任务A");
MyRunnable task2 = new MyRunnable("任务B");
// 4. 创建Thread对象,将任务对象传入
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
// 5. 启动线程
t1.start();
t2.start();
// 主线程执行
for (int i = 0; i < 5; i++) {
System.out.println("主线程正在执行:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 1. 实现Runnable接口
* 2. 实现run()方法
*/
class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "正在执行:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.3 Lambda表达式简化(Java 8+)
/**
* 使用Lambda表达式简化Runnable创建
*/
public class RunnableLambdaDemo {
public static void main(String[] args) {
// 使用Lambda表达式创建Runnable任务
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 创建并启动线程
new Thread(task, "线程A").start();
new Thread(task, "线程B").start();
}
}
4.4 实现Runnable接口的特点
| 特点 | 说明 |
|---|---|
| 避免单继承限制 | 类可以实现多个接口,更灵活 |
| 任务与线程分离 | Runnable定义任务,Thread定义线程,职责清晰 |
| 便于资源共享 | 多个线程可以共享同一个Runnable任务对象 |
| 符合面向接口编程 | 推荐的设计模式 |
五、两种方式的核心区别
5.1 代码层面对比
| 对比项 | 继承Thread类 | 实现Runnable接口 |
|---|---|---|
| 实现方式 | class MyThread extends Thread |
class MyTask implements Runnable |
| 启动方式 | new MyThread().start() |
new Thread(new MyTask()).start() |
| 资源共享 | 每个线程独立的对象 | 多个线程可共享同一个任务对象 |
| 单继承限制 | 受限制(无法继承其他类) | 无限制(可实现多个接口) |
| 获取当前线程 | this(直接使用) |
Thread.currentThread() |
| 线程名字设置 | setName()或构造方法 |
new Thread(task, name) |
5.2 设计理念对比
text
继承Thread类: ┌─────────────────┐ │ MyThread │ │ (is-a Thread) │ ← 线程就是任务本身 └─────────────────┘ 实现Runnable接口: ┌─────────────┐ ┌─────────────┐ │ MyTask │ │ Thread │ ← 线程和任务分离 │ (is-a 任务) │ ─────→│ (is-a 线程) │ (组合关系) └─────────────┘ └─────────────┘
5.3 资源共享示例对比
继承Thread类(无法共享资源):
class CounterThread extends Thread {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(getName() + " count=" + count);
}
}
}
// 测试
public class ThreadShareTest {
public static void main(String[] args) {
// 创建两个独立的对象,每个有自己的count
CounterThread t1 = new CounterThread();
CounterThread t2 = new CounterThread();
t1.start(); // 输出:Thread-0 count=1,2,3,4,5
t2.start(); // 输出:Thread-1 count=1,2,3,4,5
// 两个线程各自计数,互不影响
}
}
实现Runnable接口(可以共享资源):
class CounterRunnable implements Runnable {
private int count = 0; // 共享的资源
@Override
public void run() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + " count=" + count);
}
}
}
// 测试
public class RunnableShareTest {
public static void main(String[] args) {
// 创建一个任务对象
CounterRunnable task = new CounterRunnable();
// 多个线程共享同一个任务对象
Thread t1 = new Thread(task, "线程A");
Thread t2 = new Thread(task, "线程B");
t1.start();
t2.start();
// 两个线程共享同一个count,会产生竞态条件
// 输出:线程A count=1,2,3,4,5;线程B count=6,7,8,9,10
}
}
六、详细区别对比表
| 对比维度 | 继承Thread类 | 实现Runnable接口 |
|---|---|---|
| 灵活性 | 低(单继承限制) | 高(可实现多接口) |
| 代码耦合度 | 高(任务与线程绑定) | 低(任务与线程分离) |
| 资源共享 | 困难(需用静态变量) | 容易(自然共享) |
| 适用场景 | 简单的独立任务 | 复杂任务、资源共享场景 |
| 面向对象原则 | 违反“组合优于继承” | 符合“组合优于继承” |
| 线程池支持 | 不友好 | 友好(Runnable可放入线程池) |
| 多次执行同一任务 | 需创建多个对象 | 可多次传入不同线程 |
| 可读性 | 简单直观 | 稍复杂但更清晰 |
七、适用场景分析
7.1 继承Thread类适用场景
| 场景 | 说明 |
|---|---|
| 简单临时线程 | 只需执行一次,不需要复用 |
| 需要直接覆盖Thread的其他方法 | 如重写run()以外的interrupt()、isAlive()等 |
| 代码量很小 | 不需要复杂的任务拆分 |
// 示例:简单的一次性线程
new Thread() {
@Override
public void run() {
System.out.println("执行一次的任务");
}
}.start();
7.2 实现Runnable接口适用场景
| 场景 | 说明 |
|---|---|
| 多个线程执行相同任务 | 资源共享,如卖票系统 |
| 类已继承其他类 | 无法再继承Thread |
| 需要配合线程池使用 | ExecutorService提交Runnable |
| 任务需要复用 | 同一个Runnable可多次执行 |
| 追求高内聚低耦合设计 | 任务与线程分离 |
// 示例:多个线程共享任务(模拟卖票)
class TicketTask implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
}
}
}
// 启动多个卖票窗口
TicketTask task = new TicketTask();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
八、面试常考对比
8.1 为什么推荐使用Runnable而不是Thread?
-
避免单继承限制:Java只支持单继承,继承Thread后就无法继承其他类了
-
任务与线程解耦:Runnable专注于定义任务,Thread专注于执行任务
-
便于资源共享:多个线程可以共享同一个Runnable实例
-
便于配合线程池:线程池接受Runnable和Callable,而不是Thread
-
符合面向接口编程原则:面向接口编程比面向具体类编程更灵活
8.2 两者可以混用吗?
可以。Runnable最终还是要交给Thread来执行:
线程创建的两种方式
Lambda表达式实现Runnable接口
Runnable task = () -> System.out.println("Hello");
new Thread(task).start();
通过Lambda表达式创建Runnable实现类,简洁地定义任务逻辑。Thread构造函数接收Runnable参数,调用start()启动新线程执行。
继承Thread类重写run方法
Thread t = new Thread() {
@Override
public void run() {
System.out.println("Thread中的run");
}
};
new Thread(t).start();
Thread类本身实现了Runnable接口,其实例可作为Runnable对象传递给其他Thread。这种嵌套方式虽然合法,但通常直接调用t.start()更合理。
关键区别
- Runnable是函数式接口,适合与Lambda结合实现任务与线程控制的分离
- Thread作为Runnable的实现类,重写run()时可直接访问线程方法,但会失去Java单继承的灵活性
- 嵌套Thread实例作为参数的方式在实际开发中较少使用,容易造成理解困难
注意事项
线程启动必须调用start()而非run(),后者会在当前线程同步执行。两种方式创建的线程在功能上没有本质区别,但实现接口的方式更符合面向对象设计原则。
九、完整对比示例:银行取款
/**
* 综合示例:模拟银行取款
* 演示两种方式的区别
*/
// 方式一:继承Thread类
class ATMThread extends Thread {
private static int balance = 1000; // 静态变量实现共享
private String customerName;
public ATMThread(String customerName) {
this.customerName = customerName;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
synchronized (ATMThread.class) {
if (balance >= 100) {
balance -= 100;
System.out.println(customerName + "取款100元,余额:" + balance);
} else {
System.out.println(customerName + "取款失败,余额不足!");
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 方式二:实现Runnable接口
class ATMRunnable implements Runnable {
private int balance = 1000; // 实例变量,自然共享
private String customerName;
public ATMRunnable(String customerName) {
this.customerName = customerName;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
synchronized (this) {
if (balance >= 100) {
balance -= 100;
System.out.println(customerName + "取款100元,余额:" + balance);
} else {
System.out.println(customerName + "取款失败,余额不足!");
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class BankDemo {
public static void main(String[] args) {
System.out.println("========== 方式一:继承Thread类 ==========");
ATMThread t1 = new ATMThread("小明");
ATMThread t2 = new ATMThread("小红");
t1.start();
t2.start();
try {
Thread.sleep(1000); // 等待线程执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n========== 方式二:实现Runnable接口 ==========");
ATMRunnable task = new ATMRunnable("张三");
new Thread(task, "线程A").start();
new Thread(task, "线程B").start();
}
}
十、总结
10.1 核心知识点回顾
| 知识点 | 要点 |
|---|---|
| 继承Thread类 | 简单直接,但受单继承限制 |
| 实现Runnable接口 | 灵活、解耦、可资源共享 |
| 启动线程 | 必须调用start(),不能直接调用run() |
| 资源共享 | Runnable天然支持,Thread需静态变量 |
| 推荐方式 | 优先使用Runnable |
10.2 选择建议
text
是否需要资源共享?
├── 是 → 使用Runnable接口
└── 否 → 继续判断
├── 类是否已继承其他类?
│ ├── 是 → 使用Runnable接口
│ └── 否 → 两种都可以,但Runnable更推荐
└── 是否需要重写Thread的其他方法?
├── 是 → 使用Thread子类
└── 否 → 使用Runnable接口(推荐)
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)