|知识点: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 实现步骤

  1. 定义一个类继承Thread

  2. 重写run()方法,将线程要执行的任务代码放入其中

  3. 创建该类的实例(即创建线程对象)

  4. 调用线程对象的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 实现步骤

  1. 定义一个类实现Runnable接口

  2. 实现run()方法,将线程要执行的任务代码放入其中

  3. 创建该类的实例(即任务对象)

  4. 创建Thread对象,将任务对象作为参数传入

  5. 调用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?

  1. 避免单继承限制:Java只支持单继承,继承Thread后就无法继承其他类了

  2. 任务与线程解耦:Runnable专注于定义任务,Thread专注于执行任务

  3. 便于资源共享:多个线程可以共享同一个Runnable实例

  4. 便于配合线程池:线程池接受Runnable和Callable,而不是Thread

  5. 符合面向接口编程原则:面向接口编程比面向具体类编程更灵活

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接口(推荐)
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐