|知识点Thread类、Runnable接口、线程创建、多线程编程一、作业概述在Java中创建线程主要有两种方式方式核心类/接口实现方法方式一继承Thread类重写run()方法方式二实现Runnable接口实现run()方法传入Thread对象这两种方式都可以创建并启动线程但在设计理念、使用场景和灵活性上存在显著差异。核心问题我们应该在什么时候选择继承Thread类什么时候选择实现Runnable接口二、基础知识回顾2.1 什么是线程线程Thread是操作系统能够进行运算调度的最小单位被包含在进程之中是进程中的实际运作单位。一个进程中可以并发多个线程每条线程并行执行不同的任务。2.2 Java中的线程模型textJava线程模型 ├── 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 Threadclass 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 count1,2,3,4,5 t2.start(); // 输出Thread-1 count1,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 count1,2,3,4,5线程B count6,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需静态变量推荐方式优先使用Runnable10.2 选择建议text是否需要资源共享 ├── 是 → 使用Runnable接口 └── 否 → 继续判断 ├── 类是否已继承其他类 │ ├── 是 → 使用Runnable接口 │ └── 否 → 两种都可以但Runnable更推荐 └── 是否需要重写Thread的其他方法 ├── 是 → 使用Thread子类 └── 否 → 使用Runnable接口推荐