文章目录

思维导图

Java并发编程:线程生命周期与4种创建方式 系统性知识体系

一、整体知识体系架构图

Java线程核心基础
├─ 线程生命周期
│  ├─ 5种核心状态定义
│  ├─ 状态转换完整流程
│  ├─ 关键方法对状态的影响
│  └─ 易混淆概念辨析
└─ 线程创建的4种方式
   ├─ 继承Thread类
   ├─ 实现Runnable接口
   ├─ 实现Callable接口+FutureTask
   └─ 使用线程池(Executor框架)
   └─ 4种方式对比与选型建议

二、Java线程生命周期(面试核心考点)

2.1 线程的5种核心状态(JDK定义)

Java线程的状态在java.lang.Thread.State枚举中明确定义,共5种(注意:操作系统层面通常分为7种,不要混淆):

状态 英文 定义 核心特征
新建 NEW 线程对象已创建,但尚未调用start()方法 仅存在于JVM堆内存,未与操作系统线程绑定
可运行 RUNNABLE 线程已调用start(),正在JVM中执行或等待CPU调度 包含操作系统的"就绪(Ready)"和"运行(Running)"两个子状态
阻塞 BLOCKED 线程因等待锁(synchronized)而被阻塞 只能由"等待锁释放"事件唤醒,进入RUNNABLE状态
等待 WAITING 线程因调用wait()join()LockSupport.park()而无限期等待 必须由其他线程显式唤醒(notify()/notifyAll()/unpark()
超时等待 TIMED_WAITING 线程在指定时间内等待,超时后自动唤醒 调用wait(long)sleep(long)join(long)等方法
终止 TERMINATED 线程执行完毕或异常退出 生命周期结束,无法再次调用start()

2.2 完整状态转换流程图

新建(NEW)
   ↓ start()
可运行(RUNNABLE) ←──────────────────────────┐
   ↓ CPU调度                                  │
运行中(Running)                               │
   ├─ 正常执行完毕 → 终止(TERMINATED)         │
   ├─ 异常退出 → 终止(TERMINATED)             │
   ├─ 调用synchronized未获取锁 → 阻塞(BLOCKED) ─┘
   ├─ 调用wait() → 等待(WAITING)              │
   │   ↓ notify()/notifyAll()                 │
   │   └──────────────────────────────────────┘
   ├─ 调用wait(long) → 超时等待(TIMED_WAITING) ─┐
   │   ↓ 超时/notify()/notifyAll()             │
   │   └──────────────────────────────────────┘
   ├─ 调用sleep(long) → 超时等待(TIMED_WAITING) ─┘
   └─ 调用join() → 等待(WAITING)              │
       ↓ 被join线程执行完毕                    │
       └──────────────────────────────────────┘

2.3 关键方法对状态的影响(易混淆点)

  1. start() vs run()

    • start():启动线程,将线程从NEW转为RUNNABLE,由JVM调用run()方法
    • run():只是普通方法调用,不会启动新线程,仍在当前线程执行
  2. wait() vs sleep()(面试必问)

    对比项 wait() sleep()
    所属类 Object类 Thread类
    锁释放 释放持有的对象锁 不释放任何锁
    调用条件 必须在synchronized代码块中 任何地方都可调用
    唤醒方式 其他线程调用notify()/notifyAll()或超时 超时时间到或被interrupt()
    用途 线程间通信 线程暂停执行
  3. join()

    • 让当前线程等待调用join()的线程执行完毕后再继续
    • 底层基于wait()实现,会释放当前线程持有的锁
    • 带超时参数的join(long)会进入TIMED_WAITING状态
  4. interrupt()

    • 不会直接终止线程,只是设置线程的中断标志位
    • 对处于WAITING/TIMED_WAITING状态的线程,会抛出InterruptedException并清除中断标志
    • 对处于RUNNABLE状态的线程,仅设置标志位,需要线程主动检查isInterrupted()来响应中断

2.4 常见误区澄清

  • ❌ 误区1:线程调用start()后立即执行
    ✅ 正确:进入RUNNABLE状态,等待CPU调度器分配时间片
  • ❌ 误区2:线程终止后可以再次调用start()
    ✅ 正确:会抛出IllegalThreadStateException,一个线程只能启动一次
  • ❌ 误区3:stop()方法可以安全终止线程
    ✅ 正确:stop()已被废弃,会强制释放所有锁,导致数据不一致,应使用中断机制优雅终止

三、Java线程创建的4种方式

3.1 方式一:继承Thread类

实现步骤

  1. 定义类继承java.lang.Thread
  2. 重写run()方法,编写线程执行逻辑
  3. 创建子类实例,调用start()方法启动线程

代码示例

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }

    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start();
        thread2.start();
    }
}

优缺点

  • ✅ 优点:实现简单,直接调用this即可获取当前线程
  • ❌ 缺点:Java单继承限制,无法继承其他类;任务与线程耦合,不利于代码复用

3.2 方式二:实现Runnable接口

实现步骤

  1. 定义类实现java.lang.Runnable接口
  2. 实现run()方法,编写线程执行逻辑
  3. 创建Runnable实现类实例,作为参数传入Thread构造器
  4. 调用Thread对象的start()方法启动线程

代码示例

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }

    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread1 = new Thread(runnable, "线程1");
        Thread thread2 = new Thread(runnable, "线程2");
        thread1.start();
        thread2.start();
    }
}

优缺点

  • ✅ 优点:避免单继承限制;任务与线程分离,同一个Runnable可以被多个线程执行;符合面向接口编程思想
  • ❌ 缺点:无法获取线程执行结果;无法抛出受检异常

3.3 方式三:实现Callable接口+FutureTask

实现步骤

  1. 定义类实现java.util.concurrent.Callable<V>接口
  2. 实现call()方法,编写线程执行逻辑并返回结果
  3. 创建Callable实现类实例,包装成FutureTask<V>对象
  4. 将FutureTask对象作为参数传入Thread构造器
  5. 调用Thread对象的start()方法启动线程
  6. 通过FutureTask.get()方法获取线程执行结果(会阻塞)

代码示例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask).start();
        
        // get()方法会阻塞直到线程执行完毕返回结果
        Integer result = futureTask.get();
        System.out.println("计算结果: " + result);
    }
}

优缺点

  • ✅ 优点:可以获取线程执行结果;可以抛出受检异常;支持泛型返回值
  • ❌ 缺点:实现相对复杂;get()方法会阻塞当前线程

3.4 方式四:使用线程池(Executor框架)

实现步骤

  1. 使用java.util.concurrent.Executors工具类创建线程池
  2. 提交Runnable或Callable任务给线程池执行
  3. 关闭线程池

代码示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadPoolExample {
    public static void main(String[] args) throws Exception {
        // 创建固定大小的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        
        // 提交Runnable任务
        threadPool.submit(new MyRunnable());
        
        // 提交Callable任务并获取Future
        Future<Integer> future = threadPool.submit(new MyCallable());
        System.out.println("Callable结果: " + future.get());
        
        // 关闭线程池
        threadPool.shutdown();
    }
}

优缺点

  • ✅ 优点:
    • 避免频繁创建销毁线程,降低系统开销
    • 提高响应速度,任务到达时无需等待线程创建
    • 统一管理线程,可控制并发数,避免系统过载
    • 提供更丰富的功能:定时执行、周期执行等
  • ❌ 缺点:需要合理配置线程池参数,否则可能导致性能问题

3.5 4种创建方式对比与选型建议

对比项 继承Thread类 实现Runnable接口 实现Callable接口 使用线程池
单继承限制
返回值
异常处理 只能捕获,不能抛出 只能捕获,不能抛出 可以抛出受检异常 可以抛出受检异常
资源消耗 高(频繁创建销毁) 低(复用线程)
代码耦合度 最低
适用场景 简单场景,无需复用 多数无返回值场景 需要返回值的场景 生产环境所有并发场景

选型优先级

  1. 生产环境优先使用线程池(避免手动创建线程导致的资源耗尽问题)
  2. 简单无返回值任务使用Runnable接口
  3. 需要返回值的任务使用Callable+Future
  4. 尽量避免继承Thread类(除非需要重写Thread的其他方法)

四、核心面试高频问题清单

  1. 简述Java线程的5种状态及转换条件
  2. wait()sleep()的区别是什么?
  3. start()run()的区别是什么?
  4. 如何优雅地终止一个线程?
  5. 为什么不推荐使用stop()方法终止线程?
  6. Java创建线程有哪几种方式?各有什么优缺点?
  7. Callable和Runnable的区别是什么?
  8. 为什么推荐使用线程池而不是手动创建线程?
  9. 一个线程两次调用start()方法会发生什么?
  10. 线程的中断机制是如何工作的?

五、知识体系总结

Java线程生命周期是理解并发编程的基础,核心是掌握5种状态的定义和转换条件,以及关键方法对状态的影响。线程创建的4种方式中,前3种是基础,而线程池是生产环境的首选方案。

理解这些知识点不仅能帮助你通过面试,更能让你在实际开发中避免常见的并发问题,写出更高效、更健壮的并发代码。后续学习可以在此基础上深入了解线程同步机制、锁机制、并发容器和原子类等高级内容。


Java并发编程核心面试背诵版 + 10道高频题标准答案

第一部分:核心知识点可直接背诵版

一、Java线程生命周期(面试必背)

1. 5种核心状态(JDK Thread.State枚举定义)
  • NEW(新建):线程对象已创建,未调用start(),仅存在于JVM堆,未绑定操作系统线程
  • RUNNABLE(可运行):调用start()后进入,包含操作系统"就绪"和"运行"两个子状态
  • BLOCKED(阻塞):等待synchronized锁时进入,锁释放后自动回到RUNNABLE
  • WAITING(无限等待):调用wait()/join()/LockSupport.park()进入,必须由其他线程显式唤醒
  • TIMED_WAITING(超时等待):调用wait(long)/sleep(long)/join(long)进入,超时或被唤醒后回到RUNNABLE
  • TERMINATED(终止):线程执行完毕或异常退出,生命周期结束,无法再次启动
2. 关键方法背诵要点
  • start():唯一能启动新线程的方法,将线程从NEW转为RUNNABLE,由JVM调用run()
  • run():只是普通方法,直接调用不会启动新线程,仍在当前线程执行
  • wait():Object类方法,必须在同步块中调用,会释放对象锁,需notify()/notifyAll()唤醒
  • sleep():Thread类静态方法,任何地方可调用,不释放任何锁,超时自动唤醒
  • join():让当前线程等待目标线程执行完毕,底层基于wait()实现,会释放锁
  • interrupt():不直接终止线程,仅设置中断标志位;对等待状态线程会抛出InterruptedException并清除标志

二、Java线程创建的4种方式(面试必背)

1. 继承Thread类
  • 步骤:继承Thread → 重写run() → 创建实例 → 调用start()
  • 优点:实现简单,this即当前线程
  • 缺点:Java单继承限制,任务与线程耦合,不利于复用
2. 实现Runnable接口
  • 步骤:实现Runnable → 重写run() → 传入Thread构造器 → 调用start()
  • 优点:避免单继承,任务与线程分离,同一任务可被多个线程执行
  • 缺点:无法获取返回值,不能抛出受检异常
3. 实现Callable接口+FutureTask
  • 步骤:实现Callable → 重写call() → 包装为FutureTask → 传入Thread → 调用start() → get()获取结果
  • 优点:支持泛型返回值,可抛出受检异常
  • 缺点:实现复杂,get()方法会阻塞当前线程
4. 使用线程池(Executor框架)
  • 步骤:创建线程池 → 提交Runnable/Callable任务 → 关闭线程池
  • 优点:降低资源消耗(复用线程)、提高响应速度、统一管理线程、支持定时/周期执行
  • 缺点:需合理配置参数,否则可能导致性能问题
5. 选型优先级(生产环境)

线程池 > Callable+Future(需返回值) > Runnable(无返回值) > 继承Thread类


第二部分:10道高频面试题标准答案

1. 简述Java线程的5种状态及转换条件

标准答案
Java线程在java.lang.Thread.State枚举中定义了5种核心状态,转换流程如下:

  1. NEW → RUNNABLE:调用线程对象的start()方法
  2. RUNNABLE → BLOCKED:线程尝试获取synchronized锁但失败
  3. BLOCKED → RUNNABLE:其他线程释放锁,当前线程成功获取
  4. RUNNABLE → WAITING:调用wait()join()LockSupport.park()方法
  5. WAITING → RUNNABLE:其他线程调用notify()/notifyAll()LockSupport.unpark()
  6. RUNNABLE → TIMED_WAITING:调用wait(long)sleep(long)join(long)等带超时参数的方法
  7. TIMED_WAITING → RUNNABLE:超时时间到或被其他线程唤醒
  8. RUNNABLE → TERMINATED:线程run()方法执行完毕或抛出未捕获的异常

2. wait()sleep()的区别是什么?

标准答案
两者最核心的区别是是否释放锁,具体对比如下:

对比项 wait() sleep()
所属类 Object类 Thread类
锁行为 释放持有的对象锁 不释放任何锁
调用条件 必须在synchronized同步块/方法中 任何地方都可调用
唤醒方式 其他线程调用notify()/notifyAll()或超时 超时时间到或被interrupt()
设计目的 用于线程间通信 用于线程暂停执行

3. start()run()的区别是什么?

标准答案

  • start():是启动线程的唯一正确方法。调用后,JVM会创建一个新的操作系统线程,并将其状态从NEW转为RUNNABLE,当该线程获得CPU时间片时,JVM会自动调用其run()方法执行任务逻辑。
  • run():只是Thread类或Runnable接口定义的普通方法。直接调用run()不会启动新线程,任务逻辑会在当前调用线程中同步执行,失去了多线程的意义。

4. 如何优雅地终止一个线程?

标准答案
Java没有提供强制安全终止线程的方法,推荐使用中断机制优雅终止线程,步骤如下:

  1. 在线程内部定期检查中断标志位(Thread.currentThread().isInterrupted()
  2. 当检测到中断标志位为true时,清理资源并正常退出run()方法
  3. 对于处于WAITING/TIMED_WAITING状态的线程,捕获InterruptedException后处理中断

代码示例

public class GracefulStopThread implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            // 执行任务逻辑
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // 捕获异常后重新设置中断标志位
                Thread.currentThread().interrupt();
                break;
            }
        }
        // 清理资源
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new GracefulStopThread());
        thread.start();
        // 一段时间后中断线程
        thread.interrupt();
    }
}

5. 为什么不推荐使用stop()方法终止线程?

标准答案
stop()方法已被JDK废弃,因为它是不安全的,主要问题有:

  1. 强制释放所有锁:会立即释放线程持有的所有监视器锁,导致被锁保护的共享数据处于不一致状态
  2. 无法清理资源:线程会被立即终止,没有机会执行资源清理操作(如关闭文件、释放连接)
  3. 不可预测性:线程可能在执行任何代码时被终止,导致程序出现难以调试的bug

6. Java创建线程有哪几种方式?各有什么优缺点?

标准答案
Java创建线程主要有4种方式:

  1. 继承Thread类
    • 优点:实现简单,直接使用this即可获取当前线程
    • 缺点:受Java单继承限制,任务与线程耦合度高,不利于代码复用
  2. 实现Runnable接口
    • 优点:避免单继承限制,任务与线程分离,同一任务可被多个线程执行
    • 缺点:无法获取线程执行结果,不能抛出受检异常
  3. 实现Callable接口+FutureTask
    • 优点:支持泛型返回值,可以抛出受检异常
    • 缺点:实现相对复杂,get()方法会阻塞当前线程
  4. 使用线程池(Executor框架)
    • 优点:降低资源消耗(复用线程)、提高响应速度、统一管理线程、支持定时/周期执行
    • 缺点:需要合理配置线程池参数,否则可能导致性能问题

7. Callable和Runnable的区别是什么?

标准答案
两者都是用于定义线程执行任务的接口,主要区别如下:

对比项 Runnable Callable
方法签名 void run() V call() throws Exception
返回值 有泛型返回值
异常处理 不能抛出受检异常,只能内部捕获 可以抛出受检异常
使用方式 可直接传入Thread或线程池 需包装为FutureTask后传入Thread或线程池
结果获取 无法获取执行结果 通过Future.get()获取结果

8. 为什么推荐使用线程池而不是手动创建线程?

标准答案
手动创建线程存在以下问题:

  1. 资源消耗高:频繁创建和销毁线程会消耗大量CPU和内存资源
  2. 响应速度慢:任务到达时需要等待线程创建完成才能执行
  3. 缺乏管理:无限制创建线程会导致系统过载,甚至OOM
  4. 功能单一:不支持定时执行、周期执行等高级功能

线程池通过线程复用解决了上述问题,同时提供了统一的线程管理机制,是生产环境并发编程的首选方案。

9. 一个线程两次调用start()方法会发生什么?

标准答案
会抛出IllegalThreadStateException异常。

原因:每个线程只能启动一次。当第一次调用start()后,线程状态会从NEW变为RUNNABLE,JVM会将该线程标记为已启动。当再次调用start()时,JVM会检测到线程已经启动过,从而抛出异常。即使线程已经执行完毕进入TERMINATED状态,也不能再次调用start()

10. 线程的中断机制是如何工作的?

标准答案
Java线程中断是一种协作式的线程终止机制,不是强制终止,工作原理如下:

  1. 每个线程都有一个interrupted标志位,用于标记是否被中断
  2. 调用thread.interrupt()方法会将该线程的中断标志位设置为true
  3. 对于处于RUNNABLE状态的线程,仅设置标志位,需要线程主动检查isInterrupted()来响应中断
  4. 对于处于WAITING/TIMED_WAITING状态的线程,调用interrupt()会使其抛出InterruptedException,并清除中断标志位
  5. 可以通过Thread.interrupted()静态方法检查当前线程是否被中断,并清除中断标志位;通过isInterrupted()实例方法检查中断状态,不清除标志位

补充:面试加分技巧

  1. 回答状态转换时,主动提到"JVM的RUNNABLE状态包含操作系统的就绪和运行两个子状态",体现对底层的理解
  2. 回答wait()sleep()区别时,先说出"是否释放锁"这个核心区别,再展开其他点
  3. 回答线程创建方式时,最后一定要强调"生产环境优先使用线程池",并说明原因
  4. 回答中断机制时,主动区分interrupted()isInterrupted()的区别,展示细节掌握程度
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐