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
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
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
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
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)
上次更新: 2026/01/21, 19:29:14