Tavio's blog Tavio's blog
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Tavio Zhang

努力学习的小码喽
首页
  • JVM底层原理
  • 邪恶多线程
  • MyBatis底层原理
  • Spring底层原理
  • MySQL的优化之路
  • ClickHouse的高性能
  • Redis的快速查询
  • RabbitMQ的生产
  • Kafka的高吞吐量
  • ES的入门到入坑
  • MySQL自增ID主键空洞
  • 前端实现长整型排序
  • MySQL无感换表
  • Redis延时双删
  • 高并发秒杀优惠卷
  • AOP无侵入式告警
  • 长短链接跳转
  • 订单超时取消
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java多线程
    • 一、基础概念:进程与线程
      • 1.1 进程(Process)
      • 1.2 线程(Thread)
      • 1.3 进程与线程的核心区别
    • 二、并发与并行:线程执行的两种模式
      • 2.1 并发(Concurrency)
      • 2.2 并行(Parallelism)
      • 2.3 举例说明
    • 三、线程的生命周期:从创建到终止
      • 3.1 6种核心状态及触发条件
      • 3.2 状态转换关系
      • 3.3 关键说明
    • 四、线程的创建方式:3种核心实现
      • 4.1 方式一:继承Thread类
      • 4.2 方式二:实现Runnable接口
      • 4.3 方式三:Callable + FutureTask(带返回值)
    • 五、线程创建的关键问题解析
      • 5.1 为什么不能直接调用run()方法?
      • 5.2 为什么必须依赖Thread类?
    • 六、线程的常用属性与方法
  • Java线程锁
  • Java JMM
  • Java ThreadLocal
  • Java 线程池
  • Java CompletableFuture
  • 《多线程》笔记
Tavio
2022-03-19
目录

Java多线程

# 一、基础概念:进程与线程

在理解多线程之前,我们需要先明确两个核心概念:进程与线程,它们是操作系统中资源管理和任务执行的基本单位。

# 1.1 进程(Process)

进程是操作系统为正在运行的程序分配资源(如内存、CPU时间片、文件描述符等)的基本单位,也是程序的一次动态执行实例。

  • 举例:打开浏览器、启动微信、运行Java程序,都会创建一个独立的进程。
  • 特点:进程之间相互独立,拥有各自的内存空间和资源,进程间通信(IPC)需要通过特定机制(如管道、socket等)。

# 1.2 线程(Thread)

线程是进程内的执行实体,是CPU调度和分配的最小单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和资源(如堆内存、方法区),但拥有独立的栈空间。

  • 举例:浏览器进程中,"加载页面"、"播放视频"、"处理用户输入"分别由不同线程执行。
  • 特点:线程间通信更高效(共享内存),但也需注意并发安全问题。

# 1.3 进程与线程的核心区别

维度 进程 线程
资源分配 操作系统分配资源的基本单位 不独立分配资源(共享进程资源)
独立性 相互独立,一个崩溃不影响其他 同进程内线程共享资源,一个崩溃可能影响整个进程
通信成本 高(需跨进程机制) 低(直接共享内存)
切换开销 大(需切换资源上下文) 小(主要切换CPU上下文)

# 二、并发与并行:线程执行的两种模式

线程的执行依赖CPU调度,根据CPU核心数不同,会呈现并发或并行两种模式,二者常被混淆但本质不同。

# 2.1 并发(Concurrency)

  • 定义:多个线程交替执行,通过CPU时间片快速切换,使肉眼感觉"同时运行"。
  • 场景:单核CPU处理多个线程时,CPU在不同线程间快速切换(如1秒内切换1000次),每个线程执行一小段时间。
  • 核心:"看起来同时",本质是串行交替。

# 2.2 并行(Parallelism)

  • 定义:多个线程同时执行,依赖多核CPU,每个核心独立运行一个线程。
  • 场景:双核CPU中,核心1执行线程A,核心2执行线程B,二者真正同时运行。
  • 核心:"真正同时",依赖硬件多核支持。

# 2.3 举例说明

  • 并发:一个厨师同时处理"切菜"和"颠锅"(交替进行)。
  • 并行:两个厨师分别同时"切菜"和"颠锅"。

# 三、线程的生命周期:从创建到终止

Java线程的生命周期由java.lang.Thread.State枚举类定义,包含6种状态,状态之间通过特定操作流转,理解生命周期是掌握线程控制的基础。

# 3.1 6种核心状态及触发条件

状态 含义描述 触发条件
NEW 线程对象已创建,但未启动(未调用start()),JVM未分配实际线程资源。 Thread t = new Thread() 但未执行 t.start()
RUNNABLE 线程已启动,处于"就绪"(等待CPU调度)或"运行中"(正在CPU上执行)状态。 t.start()调用后;或从阻塞/等待状态恢复(如notify()、sleep()结束)
BLOCKED 线程因竞争synchronized锁失败而暂停,等待获取锁。 线程尝试获取synchronized锁时,锁被其他线程持有
WAITING 线程无限期等待其他线程的显式通知(无超时时间),否则永久阻塞。 调用Object.wait()、Thread.join()(无参)、LockSupport.park()等
TIMED_WAITING 线程等待指定时间后自动唤醒,或被提前通知。 调用Thread.sleep(long)、Object.wait(long)、Thread.join(long)等
TERMINATED 线程执行完成(run()方法结束)或因异常终止,生命周期结束。 run()方法正常执行完毕;或线程执行中抛出未捕获异常

# 3.2 状态转换关系

NEW
↓(调用 start (),JVM 创建线程并加入就绪队列)
RUNNABLE
├─→(竞争 synchronized 锁失败)→ BLOCKED →(其他线程释放锁并获取成功)→ RUNNABLE
├─→(调用 wait ()/join () 无超时)→ WAITING →(被 notify ()/notifyAll () 唤醒 或 被中断)→ RUNNABLE
├─→(调用 sleep (ms)/wait (ms)/join (ms))→ TIMED_WAITING →(时间到 或 被唤醒 / 中断)→ RUNNABLE
└─→(run () 执行完 或 抛出未捕获异常)→ TERMINATED
1
2
3
4
5
6
7

# 3.3 关键说明

  • RUNNABLE 不等于运行中:RUNNABLE包含"就绪"(等待CPU时间片)和"运行中"(正在执行)两种状态,仅当CPU调度到该线程时,才进入实际运行状态。
  • BLOCKED 与 WAITING 的区别:BLOCKED是因竞争synchronized锁阻塞,而WAITING是主动进入等待状态(需显式唤醒)。

# 四、线程的创建方式:3种核心实现

Java中创建线程有3种核心方式,各有适用场景,需根据是否需要返回值、是否需继承其他类等需求选择。

# 4.1 方式一:继承Thread类

步骤:继承Thread类 → 重写run()方法(线程执行逻辑)→ 创建实例并调用start()启动线程。

public class ThreadDemo1 {
    public static void main(String[] args) {
        // 创建线程实例
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        // 启动线程(JVM会调用run())
        t1.start();
        t2.start();
    }
}

// 继承Thread类,重写run()
class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行逻辑:循环打印信息
        for (int i = 0; i < 5; i++) {
            // 获取当前线程名称(默认格式:Thread-0、Thread-1...)
            System.out.println(Thread.currentThread().getName() + " : " + i);
            // 模拟耗时操作,让线程切换更明显
            try {
                Thread.sleep(1000); // 休眠1秒(进入TIMED_WAITING状态)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

优缺点:

  • 优点:实现简单,直接调用this.getName()获取线程名。
  • 缺点:Java单继承限制,继承Thread后无法再继承其他类;线程逻辑与线程对象耦合。

# 4.2 方式二:实现Runnable接口

步骤:实现Runnable接口 → 重写run()方法 → 将Runnable实例传给Thread构造器 → 调用start()启动。

public class ThreadDemo2 {
    public static void main(String[] args) {
        // 创建任务实例(线程执行逻辑)
        MyRunnable task = new MyRunnable();

        // 用Thread包装任务,可指定线程名称
        Thread t1 = new Thread(task, "线程A");
        Thread t2 = new Thread(task, "线程B");
        // 启动线程
        t1.start();
        t2.start();
    }
}

// 实现Runnable接口,重写run()
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

优缺点:

  • 优点:避免单继承限制,可多实现;任务逻辑与线程对象解耦,适合多线程共享同一个任务实例(如共享资源)。
  • 缺点:无法直接获取返回值(需额外处理)。

# 4.3 方式三:Callable + FutureTask(带返回值)

步骤:实现Callable接口 → 重写call()方法(有返回值,可抛异常)→ 用FutureTask包装Callable → 传给Thread启动 → 调用FutureTask.get()获取返回值。

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

public class ThreadDemo3 {
    public static void main(String[] args) throws Exception {
        // 创建带返回值的任务
        MyCallable task = new MyCallable();
        // 用FutureTask包装Callable(兼具Runnable和Future特性)
        FutureTask<String> futureTask = new FutureTask<>(task);

        // 启动线程(FutureTask实现了Runnable,可作为Thread构造参数)
        Thread t = new Thread(futureTask, "带返回值的线程");
        t.start();

        // 获取返回值(get()会阻塞,直到线程执行完成)
        String result = futureTask.get();
        System.out.println("线程执行结果:" + result);
    }
}

// 实现Callable接口,指定返回值类型为String
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " 执行中:" + i);
            Thread.sleep(1000);
        }
        // 返回线程名称作为结果
        return Thread.currentThread().getName() + " 执行完成";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

优缺点:

  • 优点:支持返回值;call()可抛出异常,便于错误处理;适合需要获取线程执行结果的场景(如异步计算)。
  • 缺点:实现稍复杂;get()方法会阻塞当前线程,需注意性能影响。

# 五、线程创建的关键问题解析

# 5.1 为什么不能直接调用run()方法?

  • start():是启动线程的正确方式,其作用是告诉JVM创建新线程、分配系统资源(如CPU时间片),并将线程加入就绪队列;线程被调度后,JVM会自动调用run()。
  • run():只是普通方法,直接调用run()不会创建新线程,而是在当前线程(如主线程)中同步执行run()逻辑,失去多线程意义。

# 5.2 为什么必须依赖Thread类?

无论通过Runnable还是Callable定义任务,最终都需要Thread类启动,核心原因是:

  • Thread是Java对操作系统线程的封装,通过native方法(如start0())与JVM和操作系统交互,完成底层线程创建、资源分配等操作。
  • run()(Runnable)和call()(Callable)仅定义任务逻辑,不具备向操作系统申请线程资源的能力。

# 六、线程的常用属性与方法

方法/属性 作用描述
Thread.currentThread() 获取当前正在执行的线程实例。
setName(String) / getName() 设置/获取线程名称(便于调试)。
setPriority(int) 设置线程优先级(1-10,默认5),优先级高的线程更可能被CPU调度(非绝对)。
setDaemon(boolean) 设置为守护线程(如GC线程),当所有非守护线程结束时,守护线程自动终止。
sleep(long ms) 让当前线程休眠指定毫秒数(进入TIMED_WAITING状态,不释放锁)。
join() 等待该线程执行完毕后,当前线程再继续执行(如t.join():主线程等待t执行完)。
yield() 提示CPU让出时间片,当前线程进入就绪状态,重新参与调度(不保证一定让出)。
编辑 (opens new window)
#Java多线程
上次更新: 2026/01/21, 19:29:14
Java线程锁

Java线程锁→

最近更新
01
订单超时取消
01-21
02
双 Token 登录
01-21
03
长短链接跳转
01-21
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式