乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

线程基础详解

场景:周一早上,飞翔科技的办公区一片忙碌。大翔拿着一份需求文档走到白歌面前:"白歌,我们新的薪资系统要能同时处理几千名员工的工资计算。小崔写的单线程版本跑一次得两小时,财务部都快疯了。"

白歌(推了推眼镜):"多线程啊。你先把线程生命周期搞明白,不然代码上线后死锁了可别找我。"

小崔(从工位探出头):"线程不就是 Thread.start() 吗?我用了啊!"

大翔:"你那是 Thread.start() 里调了 run() 吧,根本没异步。"

朱璐(拿着一杯咖啡悠悠飘过):"多线程可不是Thread.start一下那么简单的事情啦!线程状态、中断、join配合,每一步都有坑。"

Frank(从会议室出来):"Guys, let's also consider thread pools. But first, master the basics — lifecycle, interrupts, and the difference between start() and run()."


一、定义速查表

概念定义关键方法/状态
线程(Thread)程序中执行的独立路径,是 CPU 调度的最小单位Thread类、Runnable接口、Callable<V>接口
Thread 类继承 java.lang.Thread,重写 run() 方法start(), run(), join(), sleep(), yield(), interrupt()
Runnable 接口实现 java.lang.Runnable,解耦任务与线程void run()
Callable 接口类似 Runnable,但有返回值且可抛异常V call() throws Exception
start() vs run()start() 启动新线程调用 run();直接调用 run() 在主线程执行start() 创建新栈帧,run() 复用当前栈帧
线程生命周期NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED6 种状态,见下文状态图
sleep()静态方法,让当前线程休眠指定毫秒,不释放锁Thread.sleep(long millis)
join()等待目标线程终止,底层用 wait() 实现thread.join(), join(long millis)
yield()提示调度器当前线程愿意让出 CPU,不保证Thread.yield()
interrupt()设置线程的中断标志位;若线程在 wait/sleep/join 中则抛 InterruptedExceptionthread.interrupt(), Thread.interrupted(), isInterrupted()
守护线程(Daemon)JVM 在所有非守护线程结束后自动退出,不等待守护线程thread.setDaemon(true)
线程优先级1~10,默认 5,仅作为调度提示,不保证顺序setPriority(int), MIN_PRIORITY=1, MAX_PRIORITY=10

二、线程生命周期 —— Mermaid 状态图

状态详解(JDK 8 java.lang.Thread.State 枚举):

状态含义进入方式退出方式
NEW线程对象已创建,但尚未调用 start()new Thread()start() 调用后进入 RUNNABLE
RUNNABLE可运行的,包括正在执行和等待 CPU 调度start() 后可能进入 BLOCKED/WAITING/TERMINATED
BLOCKED等待进入 synchronized 块/方法,等待监视器锁尝试获取已被占用的对象锁获取到锁后进入 RUNNABLE
WAITING无限期等待另一个线程的通知wait(), join(), LockSupport.park()notify()/notifyAll(),被 join 线程终止,unpark()
TIMED_WAITING带超时的等待sleep(ms), wait(ms), join(ms), parkNanos()超时到期或收到通知
TERMINATED线程执行完毕或异常退出run() 方法返回或抛出未捕获异常不可恢复,线程对象可被 GC

三、Thread 类 vs Runnable 接口 —— 完整对比

维度继承 Thread 类实现 Runnable 接口
耦合度高,任务与线程绑定低,任务与线程分离,可提交给线程池
扩展性单继承限制,无法再继承其他类可实现多接口,灵活扩展
资源共享需要 static 变量或外部对象同一个 Runnable 实例可被多个线程共享
线程池兼容不兼容(每个 new Thread() 都是独立线程)兼容,Runnable 可直接提交给 ExecutorService
返回值run() 无返回值run() 无返回值,需 Callable 获取返回值
使用建议不推荐,除非必须扩展 Thread 类的特殊行为推荐,面向接口编程

四、完整可运行示例

示例 1:飞翔科技多线程处理员工工资计算

场景:飞翔科技有 1000 名员工,财务部需要计算每人当月工资(含基本工资+绩效+加班)。大翔用多线程分片计算。

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 飞翔科技薪酬计算系统 — 多线程分片处理员工工资
 * 
 * 场景:1000 名员工,分 5 个线程并行计算,最后汇总
 */
public class SalaryCalculator {

    // 员工实体
    static class Employee {
        int id;
        String name;
        double baseSalary;   // 基本工资
        double performance;  // 绩效
        double overtime;     // 加班费
        double total;        // 合计

        public Employee(int id, String name, double baseSalary) {
            this.id = id;
            this.name = name;
            this.baseSalary = baseSalary;
        }

        public void calculate() {
            // 模拟计算逻辑:绩效 = 基本工资 * 随机系数(0.8~1.5)
            this.performance = baseSalary * (0.8 + Math.random() * 0.7);
            // 加班费 = 基本工资 * 0.3 * 随机
            this.overtime = baseSalary * 0.3 * Math.random();
            this.total = baseSalary + performance + overtime;
            // 模拟计算耗时
            try { Thread.sleep(1); } catch (InterruptedException e) { }
        }

        @Override
        public String toString() {
            return String.format("员工[%d] %s: 基本=%.0f, 绩效=%.0f, 加班=%.0f, 合计=%.0f",
                    id, name, baseSalary, performance, overtime, total);
        }
    }

    // 工资计算线程 — 继承 Thread
    static class SalaryThread extends Thread {
        private final List<Employee> employees;
        private final int threadId;

        public SalaryThread(List<Employee> employees, int threadId) {
            this.employees = employees;
            this.threadId = threadId;
        }

        @Override
        public void run() {
            System.out.printf("[线程-%d] 开始处理 %d 名员工的工资...\n",
                    threadId, employees.size());
            long start = System.currentTimeMillis();
            for (Employee emp : employees) {
                emp.calculate();
            }
            long end = System.currentTimeMillis();
            System.out.printf("[线程-%d] 处理完成,耗时: %d ms\n",
                    threadId, (end - start));
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 1. 生成 1000 名员工数据
        List<Employee> allEmployees = new ArrayList<>();
        String[] names = {"张伟", "李娜", "王芳", "赵强", "刘洋", "陈静", "杨帆", "黄莉",
                          "周明", "吴秀", "徐涛", "孙华", "马良", "朱峰", "胡波", "林雨",
                          "何雪", "郭瑞", "高洁", "罗平"};
        Random random = new Random();
        for (int i = 0; i < 200; i++) {
            String name = names[i % names.length] + (i + 1);
            double base = 5000 + random.nextInt(15000); // 5k~20k 基本工资
            allEmployees.add(new Employee(i + 1, name, base));
        }

        System.out.println("========== 飞翔科技薪酬计算系统 (多线程版) ==========");
        System.out.println("总员工数: " + allEmployees.size());
        System.out.println();

        // 2. 分片:分成 5 个线程
        int threadCount = 5;
        int chunkSize = allEmployees.size() / threadCount;
        SalaryThread[] threads = new SalaryThread[threadCount];

        long totalStart = System.currentTimeMillis();

        // 3. 创建并启动线程
        for (int i = 0; i < threadCount; i++) {
            int fromIndex = i * chunkSize;
            int toIndex = (i == threadCount - 1) ? allEmployees.size() : (i + 1) * chunkSize;
            List<Employee> chunk = allEmployees.subList(fromIndex, toIndex);
            threads[i] = new SalaryThread(chunk, i + 1);
            threads[i].start();  // ★ 注意:调用 start() 而不是 run()
        }

        // 4. join() — 等待所有线程完成
        for (int i = 0; i < threadCount; i++) {
            threads[i].join();  // 主线程阻塞,等待子线程 i 完成
        }

        long totalEnd = System.currentTimeMillis();

        // 5. 汇总结果
        double grandTotal = 0;
        for (Employee emp : allEmployees) {
            grandTotal += emp.total;
        }

        System.out.println("\n========== 汇总结果 ==========");
        System.out.printf("应发工资总额: %.2f 元\n", grandTotal);
        System.out.printf("总耗时: %d ms\n", (totalEnd - totalStart));

        // 打印前 5 个员工明细
        System.out.println("\n--- 部分员工工资明细(前5名) ---");
        for (int i = 0; i < 5; i++) {
            System.out.println(allEmployees.get(i));
        }
    }
}

控制台输出(示例):

========== 飞翔科技薪酬计算系统 (多线程版) ==========
总员工数: 200

[线程-1] 开始处理 40 名员工的工资...
[线程-2] 开始处理 40 名员工的工资...
[线程-3] 开始处理 40 名员工的工资...
[线程-4] 开始处理 40 名员工的工资...
[线程-5] 开始处理 40 名员工的工资...
[线程-4] 处理完成,耗时: 48 ms
[线程-3] 处理完成,耗时: 50 ms
[线程-1] 处理完成,耗时: 52 ms
[线程-5] 处理完成,耗时: 53 ms
[线程-2] 处理完成,耗时: 55 ms

========== 汇总结果 ==========
应发工资总额: 2918456.32 元
总耗时: 56 ms

--- 部分员工工资明细(前5名) ---
员工[1] 张伟1: 基本=12000, 绩效=15000, 加班=2300, 合计=29300
员工[2] 李娜2: 基本=8000, 绩效=7200, 加班=1200, 合计=16400
...

示例 2:飞翔科技多客服并发处理用户请求 + interrupt 优雅停止

场景:飞翔科技客服系统,多个客服线程并行处理用户工单。下班时间到了,大翔需要通过 interrupt() 优雅地停止所有客服线程。

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 飞翔科技客服工单处理系统 — 多线程 + interrupt 优雅停止
 */
public class CustomerServiceSystem {

    // 工单计数器(线程安全)
    static final AtomicInteger ticketCounter = new AtomicInteger(0);
    // 全局运行标志
    static volatile boolean systemRunning = true;

    // 客服线程 — 实现 Runnable(推荐方式)
    static class CustomerServiceAgent implements Runnable {
        private final String agentName;

        public CustomerServiceAgent(String agentName) {
            this.agentName = agentName;
        }

        @Override
        public void run() {
            System.out.printf("[%s] 上线,开始处理工单...\n", agentName);

            while (systemRunning && !Thread.currentThread().isInterrupted()) {
                // 取出一个工单
                int ticketId = ticketCounter.incrementAndGet();
                System.out.printf("[%s] 正在处理工单 #%d\n", agentName, ticketId);

                // 模拟处理耗时
                try {
                    Thread.sleep(500); // 模拟处理 500ms
                } catch (InterruptedException e) {
                    // ★ interrupt 捕获:sleep 被中断
                    System.out.printf("[%s] 收到中断信号(工单 #%d 处理中),准备下线...\n",
                            agentName, ticketId);
                    // 恢复中断标志(因为 catch 会清除标志)
                    Thread.currentThread().interrupt();
                    break;
                }

                System.out.printf("[%s] 工单 #%d 处理完成 ✓\n", agentName, ticketId);
            }

            System.out.printf("[%s] 已下线,共处理工单。\n", agentName);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========== 飞翔科技客服工单系统 ==========\n");

        // 创建 3 个客服线程
        String[] agentNames = {"大翔", "白歌", "小崔"};
        Thread[] agents = new Thread[agentNames.length];

        for (int i = 0; i < agentNames.length; i++) {
            agents[i] = new Thread(
                    new CustomerServiceAgent(agentNames[i]),
                    "客服-" + agentNames[i]
            );
            agents[i].start();
        }

        // 主线程让系统运行 3 秒,然后发起中断
        System.out.println("[系统] 客服系统运行中...\n");
        Thread.sleep(3000);

        System.out.println("\n[系统] 下班时间到!通知所有客服下线...\n");

        // ★ 第一步:设置全局标志
        systemRunning = false;

        // ★ 第二步:中断所有客服线程(处理那些正在 sleep 的线程)
        for (Thread agent : agents) {
            agent.interrupt();
        }

        // ★ 第三步:等待所有客服线程完成
        for (Thread agent : agents) {
            agent.join(2000); // 最多等 2 秒
        }

        System.out.println("\n[系统] 所有客服已下线。今天处理了 " 
                + ticketCounter.get() + " 个工单。");
    }
}

控制台输出(示例):

========== 飞翔科技客服工单系统 ==========

[大翔] 上线,开始处理工单...
[大翔] 正在处理工单 #1
[白歌] 上线,开始处理工单...
[白歌] 正在处理工单 #2
[小崔] 上线,开始处理工单...
[小崔] 正在处理工单 #3
[系统] 客服系统运行中...

[大翔] 工单 #1 处理完成 ✓
[大翔] 正在处理工单 #4
[白歌] 工单 #2 处理完成 ✓
[白歌] 正在处理工单 #5
[小崔] 工单 #3 处理完成 ✓
[小崔] 正在处理工单 #6
...

[系统] 下班时间到!通知所有客服下线...

[小崔] 收到中断信号(工单 #6 处理中),准备下线...
[大翔] 收到中断信号(工单 #4 处理中),准备下线...
[白歌] 收到中断信号(工单 #5 处理中),准备下线...
[小崔] 已下线
[大翔] 已下线
[白歌] 已下线

[系统] 所有客服已下线。今天处理了 18 个工单。

五、易错场景

易错 1:直接调用 run() 而非 start()

// ✗ 错误:直接调用 run() — 不会创建新线程!
Thread t = new Thread(() -> System.out.println("新线程"));
t.run();   // 在 main 线程执行,输出 "新线程",但没有并发
System.out.println("main 继续");  // 会在 run() 执行完才输出

// ✓ 正确:调用 start() — 创建新线程
Thread t2 = new Thread(() -> System.out.println("新线程"));
t2.start(); // 新线程异步执行
System.out.println("main 继续");  // 可能先于或后于 "新线程" 输出 — 这才是并发

后果:run() 直接调用不会创建新线程,代码在主线程中顺序执行,丧失了多线程的意义。在飞翔科技的薪酬计算系统中,如果小崔调用了 run() 而不是 start(),所有 1000 名员工的工资将在主线程中串行计算,耗时和单线程一样。


易错 2:join() 使用不当导致死等

// ✗ 错误:线程自己 join 自己 — 永远等待
Thread t = new Thread(() -> {
    try {
        Thread.currentThread().join(); // ★ 线程等待自身结束 → 死锁!
    } catch (InterruptedException e) { }
});
t.start();

// ✗ 错误:join() 顺序不当 — 两个线程互相等待
Thread t1 = new Thread(() -> {
    try { t2.join(); } catch (InterruptedException e) { } // t1 等 t2
});
Thread t2 = new Thread(() -> {
    try { t1.join(); } catch (InterruptedException e) { } // t2 等 t1
});
t1.start(); t2.start();
// t1 等 t2 结束,t2 等 t1 结束 → 死锁!

// ✓ 正确:join() 用于父线程等待子线程
Thread worker = new Thread(() -> { /* 耗时任务 */ });
worker.start();
worker.join();  // 父线程等待 worker 完成
System.out.println("worker 已完成");

易错 3:interrupt() 标志位被吞掉

// ✗ 错误:catch InterruptedException 后不恢复中断标志
public void run() {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // ★ 吞掉了中断!循环会继续执行
            System.out.println("被中断了...但是我还继续跑");  // 没有 break,没有恢复标志
        }
    }
}

// ✓ 正确方式一:捕获后 break
public void run() {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            break;  // 直接退出循环
        }
    }
}

// ✓ 正确方式二:恢复中断标志
public void run() {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // ★ 恢复标志,下次循环判断退出
        }
    }
}

原因:当 InterruptedException 被抛出后,JVM 会清除线程的中断标志位。如果不恢复标志或手动 break,线程永远不会停止 —— 在飞翔科技的客服系统中,大翔下班后点击"停止",但客服线程仍在继续处理工单。


易错 4:sleep() 不释放锁

// ✗ 误解:以为 sleep() 会释放锁
synchronized (lock) {
    Thread.sleep(5000); // ★ sleep 期间仍然持有 lock 的监视器锁!
    // 其他线程在这 5 秒内无法进入该 synchronized 块
}

// ✓ 如果需要释放锁等待,使用 wait()
synchronized (lock) {
    lock.wait(5000); // ★ wait 会释放锁,其他线程可以获取 lock
}

关键区别:sleep() 是 Thread 的静态方法,让线程休眠但不释放任何锁。wait() 是 Object 的方法,必须在 synchronized 块中调用,且调用后会释放锁。


六、面试考点

Q1:请描述 Java 线程的 6 种状态,以及状态之间如何转换?

答:

状态含义转换触发条件
NEW线程对象创建但未 startnew Thread() → 调用 start() 后进入 RUNNABLE
RUNNABLE可运行(含就绪和运行中)CPU 调度分配时间片;获取锁后从 BLOCKED 返回
BLOCKED等待进入 synchronized未竞争到监视器锁;获取锁后回到 RUNNABLE
WAITING无限期等待通知wait()/join()/park() → notify()/notifyAll()/unpark() 后回到 RUNNABLE
TIMED_WAITING有限期等待sleep(ms)/wait(ms)/join(ms) → 超时或通知后回到 RUNNABLE
TERMINATED线程终止run() 方法执行完毕或异常退出,不可恢复

状态转换图(见上文 Mermaid 图)。特别注意:

  • yield() 不会改变线程状态,线程保持在 RUNNABLE
  • BLOCKED 特指 synchronized 锁竞争,Lock 接口导致的是 WAITING
  • 从 WAITING/TIMED_WAITING 回到 RUNNABLE 后,仍需重新竞争 CPU 时间片

Q2:start() 和 run() 有什么区别?

答:

  • start():由 JVM 调用本地方法创建一个新的操作系统线程,然后在新线程中回调 run() 方法。这是实现多线程并发执行的唯一正确入口。
  • run():仅是一个普通 Java 方法。直接调用 run() 不会创建新线程,代码在当前线程(如 main 线程)中顺序执行。
  • 每个 Thread 对象的 start() 只能调用一次,重复调用会抛出 IllegalThreadStateException。
  • 底层原理:start() → native start0() → OS 创建线程 → JVM 回调 run()。

Q3:sleep() 和 wait() 的核心区别是什么?

答:

维度sleep()wait()
所属类java.lang.Thread 静态方法java.lang.Object 实例方法
锁释放不释放任何锁释放当前对象监视器锁
调用条件任何地方都可以调用必须在 synchronized 块/方法中调用
唤醒方式超时自动唤醒或 interrupt()notify()/notifyAll() 或超时(带参数版本)
用途暂停执行,不涉及线程通信线程间通信,等待条件满足

Q4:interrupt()、interrupted()、isInterrupted() 的区别?

答:

// interrupt() — 实例方法,设置目标线程的中断标志位
thread.interrupt(); // 如果线程在 sleep/wait/join 中,会抛出 InterruptedException

// interrupted() — 静态方法,检测当前线程的中断状态并清除标志位
boolean wasInterrupted = Thread.interrupted(); // ★ 返回后标志位被清除

// isInterrupted() — 实例方法,检测目标线程的中断状态,不清除标志位
boolean isInterrupted = thread.isInterrupted(); // ★ 标志位不变

关键陷阱:InterruptedException 被捕获后,JVM 会清除中断标志位。如果需要在捕获后保留中断状态,必须手动恢复:Thread.currentThread().interrupt();


七、JDK 8 关键 API 速查

// 线程创建方式 1:继承 Thread
class MyThread extends Thread {
    @Override public void run() { /* 任务 */ }
}
new MyThread().start();

// 线程创建方式 2:实现 Runnable(推荐)
new Thread(() -> { /* 任务 */ }, "线程名").start();

// 线程创建方式 3:Callable + FutureTask(有返回值)
FutureTask<String> task = new FutureTask<>(() -> "结果");
new Thread(task).start();
String result = task.get(); // 阻塞等待

// 线程基本操作
Thread.sleep(1000);           // 当前线程休眠 1 秒
thread.join();                // 等待 thread 线程终止
thread.join(5000);            // 最多等 5 秒
Thread.yield();               // 提示调度器让出 CPU
thread.interrupt();           // 中断目标线程
thread.setDaemon(true);       // 设为守护线程(必须在 start 前)
thread.setPriority(Thread.MAX_PRIORITY); // 设置优先级 1~10

// 线程状态查询
Thread.State state = thread.getState();
thread.isAlive();             // 线程是否存活
thread.isInterrupted();       // 是否被中断(不清除标志)
Thread.interrupted();         // 当前线程是否被中断(清除标志)

// UncaughtExceptionHandler — 处理未捕获异常
thread.setUncaughtExceptionHandler((t, e) -> {
    System.err.println("线程 " + t.getName() + " 异常: " + e.getMessage());
});

后记:大翔用了三天时间把多线程基础吃透,薪资计算系统从 2 小时优化到了 8 分钟。小崔在旁边嘀咕:"原来 start() 和 run() 真的不一样啊……"

白歌:"这还只是开始。线程安全、锁机制、线程池——那才是真正的水深区。"

朱璐(微笑):"线程池的重要性,你们以后就懂了。现在先把 lifecycle 和 interrupt 弄明白吧。"

上一页
第十六章 多线程与并发 —— 章节导读
下一页
synchronized 详解