1 基础概念
1.1 进程和线程
1.1.1 进程(Process)
进程指的是程序在操作系统里的一次执行过程,它是系统进行资源分配和调度的基本单位。进程具备自己独立的内存空间、系统资源以及执行上下文。下面是进程的一些主要特点:
- 独立性:不同的进程之间相互隔离,一个进程无法直接访问另一个进程的内存和资源。
- 资源分配:操作系统会给每个进程分配独立的内存区域、文件描述符等资源。
- 重量级:进程的创建和销毁开销相对较大,因为需要进行内存分配和上下文切换。
- 通信方式:进程间通信(IPC)要借助管道、消息队列、共享内存等机制来实现。
在 Java 中,可以通过ProcessBuilder
或者Runtime.getRuntime().exec()
方法来创建和控制外部进程。以下是一个简单的示例:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ProcessExample {
public static void main(String[] args) {
try {
// 创建一个进程来执行系统命令(以Windows的ipconfig为例)
Process process = Runtime.getRuntime().exec("ipconfig");
// 获取进程的输出流并读取
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 等待进程执行完毕并获取返回值
int exitCode = process.waitFor();
System.out.println("进程退出码: " + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
1.1.2 线程(Thread)
线程是进程中的一个执行单元,也被称作轻量级进程。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源,但各自拥有独立的程序计数器、栈空间和局部变量。线程的主要特点如下:
- 共享资源:同一进程内的线程可以共享堆内存、静态变量等资源。
- 轻量级:线程的创建和切换开销较小,因为不需要重新分配内存和系统资源。
- 并发执行:在多核 CPU 环境下,多个线程能够实现真正的并行执行。
- 同步问题:由于线程共享资源,所以需要通过同步机制(如
synchronized
、Lock
)来避免数据竞争和不一致的问题。
在 Java 中,创建线程主要有两种方式:
- 继承 Thread 类:
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行中: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
System.out.println("主线程执行中: " + Thread.currentThread().getName());
}
}
- 实现 Runnable 接口:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行中: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // 启动线程
System.out.println("主线程执行中: " + Thread.currentThread().getName());
}
}
1.1.3 进程与线程的区别
比较维度 |
进程 |
线程 |
资源占用 |
拥有独立的内存和系统资源 |
共享进程资源,仅拥有自己的栈和程序计数器 |
调度 |
进程是操作系统进行资源分配和调度的基本单位 |
线程是 CPU 调度和分派的基本单位 |
并发性 |
不同进程之间可以并发执行 |
同一进程内的多个线程可以并发执行 |
通信方式 |
进程间通信(IPC),如管道、消息队列等 |
直接共享内存,通过同步机制通信 |
上下文切换开销 |
开销大 |
开销小 |
创建和销毁开销 |
开销大 |
开销小 |
1.1.4 线程的生命周期
Java 线程的生命周期包含以下几种状态:
- 新建(New):线程对象被创建,但还没有调用
start()
方法。 - 就绪(Runnable):线程已经启动,正在等待 CPU 时间片。
- 运行(Running):线程获得 CPU 执行权,正在执行
run()
方法中的代码。 - 阻塞(Blocked):线程因为等待锁、IO 操作等原因暂时停止执行。
- 等待(Waiting):线程调用了
wait()
、join()
等方法,进入无限期等待状态。 - 超时等待(Timed Waiting):线程调用了
wait(long)
、sleep(long)
等方法,在指定时间后会自动恢复。 - 终止(Terminated):线程的
run()
方法执行完毕或者因为异常退出。
1.2 并发和并行
1.2.1 并发(Concurrency)
并发是指在同一时间段内,系统能够处理多个任务的能力。这些任务在宏观上看似是同时执行的,但在微观层面,它们可能是交替执行的。并发的核心在于任务的切换和调度,通过快速切换执行上下文,让用户感觉多个任务在同时进行。
特点:
- 多个任务在逻辑上同时执行,但物理上可能是串行的。
- 适用于 I/O 密集型场景(如网络请求、文件读写),因为任务在等待 I/O 时可以让出 CPU 资源。
- 通过线程、协程等轻量级执行单元实现。
示例场景:
- 浏览器同时处理多个标签页的加载。
- 服务器同时响应多个客户端请求。
Java 实现:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrencyExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 提交两个任务
executor.submit(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("任务1: " + i);
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
executor.submit(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("任务2: " + i);
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
executor.shutdown();
}
}
输出可能是任务 1 和任务 2 交替执行,体现了并发的特性。
1.2.2 并行(Parallelism)
并行是指在同一时刻,系统能够真正同时执行多个任务的能力。并行需要依赖多核 CPU 等硬件资源,每个任务分配到独立的 CPU 核心上执行,不存在上下文切换的开销。
特点:
- 多个任务在物理上同时执行,需要多核 CPU 支持。
- 适用于 CPU 密集型场景(如科学计算、图像处理),可以充分利用多核资源加速计算。
- 通过多进程、多线程或 GPU 等方式实现。
示例场景:
- 视频渲染时多个线程同时处理不同帧。
- 数据库并行查询多个分片数据。
Java 实现:
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
// 计算斐波那契数列的并行实现
class Fibonacci extends RecursiveTask<Integer> {
final int n;
Fibonacci(int n) { this.n = n; }
@Override
protected Integer compute() {
if (n <= 1)
return n;
Fibonacci f1 = new Fibonacci(n - 1);
f1.fork();
Fibonacci f2 = new Fibonacci(n - 2);
return f2.compute() + f1.join();
}
}
public class ParallelismExample {
public static void main(String[] args) {
ForkJoinPool pool = ForkJoinPool.commonPool();
int result = pool.invoke(new Fibonacci(10));
System.out.println("斐波那契数列第10项: " + result);
}
}
通过 Fork/Join 框架将任务分解为多个子任务并行执行。
1.2.3 并发与并行的区别
比较维度 |
并发(Concurrency) |
并行(Parallelism) |
核心思想 |
处理多个任务的能力(任务切换) |
同时执行多个任务的能力(物理并行) |
时间维度 |
宏观同时,微观交替 |
真正的同时执行 |
硬件依赖 |
单核或多核 CPU 均可 |
必须依赖多核 CPU |
应用场景 |
I/O 密集型任务(如 Web 服务器) |
CPU 密集型任务(如科学计算) |
实现方式 |
线程、协程、事件循环 |
多进程、多线程、GPU 计算 |
目的 |
提高资源利用率,增强系统响应性 |
加速计算,提升吞吐量 |
1.2.4 Java 中的并发与并行工具
- 线程池(ThreadPool):
ExecutorService executor = Executors.newFixedThreadPool(10);
- 并行流(Parallel Stream):
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
- CompletableFuture:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "结果1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "结果2");
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
allFutures.thenRun(() -> {
System.out.println(future1.join() + " " + future2.join());
});
- Fork/Join 框架:
ForkJoinPool pool = new ForkJoinPool();
RecursiveTask<Integer> task = new MyRecursiveTask(0, 1000);
int result = pool.invoke(task);
1.2.5 总结
- 并发是一种编程模型,通过任务切换提高资源利用率,适合处理多任务的场景。
- 并行是一种物理实现,依赖多核硬件同时执行多个任务,适合加速计算。
- 现代系统通常同时使用并发和并行:
- 服务器通过并发处理大量客户端请求(如 Web 服务器)。
- 计算密集型任务通过并行加速(如图像处理、机器学习)。
1.3 同步和异步
1.3.1 同步(Synchronous)
同步是指代码按照顺序依次执行的模式,每个操作必须等待前一个操作完成后才能开始。在同步编程中,调用者会阻塞直到被调用的操作执行完毕并返回结果。
特点:
- 代码执行顺序明确,易于理解和调试。
- 存在阻塞现象,可能导致程序在等待 I/O 时浪费 CPU 资源。
- 适用于逻辑简单、依赖强的场景。
示例场景:
- 读取文件后立即处理内容。
- 发送 HTTP 请求后等待响应再继续执行。
Java 同步代码示例:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class SynchronousExample {
public static void main(String[] args) {
try {
// 同步发送HTTP请求
URL url = new URL("https://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 阻塞直到响应返回
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// 响应处理完成后继续执行
System.out.println("请求处理完毕,继续执行后续代码");
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码会阻塞在getInputStream()
直到服务器返回响应。
1.3.2 异步(Asynchronous)
异步是指代码执行时不需要等待当前操作完成,而是可以继续执行后续代码,被调用的操作会在后台完成后通过回调、事件或 Future 等方式通知调用者。
特点:
- 非阻塞执行,提高程序的并发能力和响应速度。
- 代码逻辑分散,可能增加理解和调试难度。
- 适用于 I/O 密集型、耗时操作的场景。
示例场景:
- 浏览器异步加载图片,同时不影响页面渲染。
- 服务器异步处理请求,提高吞吐量。
Java 异步代码示例(使用 CompletableFuture):
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class AsynchronousExample {
public static void main(String[] args) {
// 异步执行任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
// 模拟耗时操作
Thread.sleep(2000);
return "异步操作结果";
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
// 主线程继续执行
System.out.println("主线程继续执行其他任务");
// 注册回调函数
future.thenAccept(result -> {
System.out.println("异步操作完成,结果: " + result);
});
// 可选:阻塞等待结果(如果需要)
try {
String result = future.get();
System.out.println("获取到结果: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
主线程不会阻塞,可以继续执行其他任务,异步操作完成后通过thenAccept
回调通知。
1.3.3 同步与异步的区别
比较维度 |
同步(Synchronous) |
异步(Asynchronous) |
执行模式 |
按顺序依次执行,前一个操作完成后才开始下一个 |
不等待当前操作完成,继续执行后续代码 |
阻塞特性 |
调用者会被阻塞直到操作完成 |
调用者不会被阻塞 |
响应性 |
可能因等待耗时操作而降低整体响应性 |
提高系统并发能力和响应速度 |
代码复杂度 |
逻辑简单,易于理解和调试 |
逻辑分散(回调、Future 等),调试难度较高 |
适用场景 |
逻辑简单、依赖强的操作 |
I/O 密集型、耗时操作的场景 |
资源利用 |
等待期间 CPU 资源浪费(尤其 I/O 操作) |
等待期间 CPU 可处理其他任务 |
1.3.4 异步编程模式
常见的异步编程模式包括:
- 回调函数(Callback):
- 优点:简单直接。
- 缺点:嵌套过深会导致 "回调地狱"(Callback Hell)。
asyncOperation(param, (result, error) -> {
if (error != null) {
// 处理错误
} else {
// 处理结果
}
});
- Future/Promise:
- 优点:避免回调地狱,提供更优雅的结果获取方式。
- 缺点:仍需显式处理阻塞(如调用
get()
)。
Future<String> future = executor.submit(() -> {
// 执行异步操作
return "结果";
});
// 稍后获取结果(可能阻塞)
String result = future.get();
- CompletableFuture(Java 8+):
- 优点:支持链式调用、组合多个 Future、异常处理。
- 示例见前文。
- 协程(Coroutine):
- 优点:轻量级,避免线程切换开销,代码结构接近同步。
- 示例(Java 19 + 虚拟线程):
Thread.startVirtualThread(() -> {
// 异步执行代码
String result = performIO();
System.out.println("结果: " + result);
});
1.3.5 同步与异步的应用场景
- 同步适用场景:
- 操作之间存在强依赖关系(如数据库事务)。
- 资源竞争严重,需要严格顺序执行。
- 简单业务逻辑,无需高并发。
- 异步适用场景:
- I/O 密集型操作(如网络请求、文件读写)。
- 耗时计算(如大数据处理、AI 推理)。
- 需要高吞吐量的场景(如 Web 服务器、消息队列)。
- 用户界面响应性要求高的场景(如前端交互、移动应用)。
1.3.4 总结
- 同步是代码按顺序执行的模式,简单直观但可能导致阻塞。
- 异步是非阻塞执行模式,通过回调、Future 等机制提高并发和响应性。
- 现代应用通常结合使用同步和异步:
- 核心业务逻辑使用同步保证正确性和可维护性。
- I/O 密集型操作使用异步提高性能。
2 线程创建方式
2.1 继承 Thread 类
实现步骤:
- 创建一个类,继承自
Thread
类。 - 重写
run()
方法,在这个方法里定义线程要执行的任务。 - 创建该类的实例,然后调用
start()
方法启动线程。
示例代码:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " 执行: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
特点:
- 编码较为简单,直接继承
Thread
类就行。 - 由于 Java 是单继承,继承了
Thread
类后就不能再继承其他类,这会限制代码的扩展性。 - 线程和任务是耦合在一起的,线程的逻辑被封装在
Thread
子类中。
2.2 实现 Runnable 接口
实现步骤:
- 创建一个类,实现
Runnable
接口。 - 实现
run()
方法,在其中定义任务逻辑。 - 创建
Runnable
实现类的实例,将其作为参数传递给Thread
类的构造函数。 - 调用
Thread
实例的start()
方法启动线程。
示例代码:
public 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(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyRunnable task = new MyRunnable();
Thread thread1 = new Thread(task, "线程1");
Thread thread2 = new Thread(task, "线程2");
thread1.start();
thread2.start();
}
}
特点:
- 避免了单继承的限制,实现类还能继承其他类并实现多个接口。
- 任务和线程是分离的,符合面向对象的设计原则,提高了代码的可复用性。
- 推荐使用这种方式,因为它更灵活,能更好地体现 "数据与逻辑分离" 的思想。
2.3 实现 Callable 接口
实现步骤:
- 创建一个类,实现
Callable<V>
接口,这里的V
是返回值的类型。 - 实现
call()
方法,该方法可以有返回值,并且能够抛出异常。 - 创建
Callable
实现类的实例,把它包装到FutureTask
中。 - 将
FutureTask
实例作为参数传递给Thread
类的构造函数。 - 调用
start()
方法启动线程,通过FutureTask
的get()
方法获取返回值。
示例代码:
import java.util.concurrent.*;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable task = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask);
thread.start();
// 获取任务的返回值(会阻塞,直到任务完成)
Integer result = futureTask.get();
System.out.println("计算结果: " + result);
}
}
特点:
call()
方法支持返回值,还能抛出受检查异常。- 通过
FutureTask
可以获取异步计算的结果,在需要处理返回值的场景中很有用。 - 适用于需要返回执行结果的异步任务。
2.4 使用 CompletableFuture(Java 8+)
实现步骤:
- 通过
CompletableFuture
的静态方法创建异步任务。 - 可以使用
thenApply()
、thenAccept()
、thenCompose()
等方法进行链式操作。 - 可以使用
join()
或get()
方法获取结果。
示例代码:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "异步操作结果";
});
// 链式操作:转换结果
CompletableFuture<String> processedFuture = future.thenApply(result -> {
return result.toUpperCase();
});
// 异步回调:结果处理完成后执行
processedFuture.thenAccept(finalResult -> {
System.out.println("最终结果: " + finalResult);
});
// 阻塞获取结果(如果需要)
System.out.println("等待结果...");
System.out.println(processedFuture.get());
}
}
特点:
- 提供了函数式编程风格,支持链式调用,代码更加简洁。
- 内置了线程池管理,默认使用
ForkJoinPool.commonPool()
。 - 支持组合多个异步任务,如
allOf()
、anyOf()
等。 - 适合复杂的异步流程编排,如并行执行多个任务然后合并结果。
2.5 使用虚拟线程(Virtual Threads,Java 19 +)
实现步骤:
- 创建
Runnable
或Callable
任务。 - 通过
Thread.startVirtualThread()
方法启动虚拟线程。 - 虚拟线程会在平台线程上运行,由 JVM 自动管理。
示例代码:
public class VirtualThreadExample {
public static void main(String[] args) throws InterruptedException {
// 创建大量虚拟线程
for (int i = 0; i < 1000; i++) {
final int taskId = i;
Thread.startVirtualThread(() -> {
System.out.println("虚拟线程 " + taskId + " 开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("虚拟线程 " + taskId + " 执行完毕");
});
}
// 主线程等待所有虚拟线程完成
Thread.sleep(5000);
}
}
特点:
- 虚拟线程是轻量级的线程,由 JVM 管理,创建和销毁的成本极低。
- 非常适合高并发、I/O 密集型的场景,能显著提高系统吞吐量。
- 与传统线程 API 兼容,使用方式和普通线程类似。
- 需要 Java 19 及以上版本,并且启用
--enable-preview
选项。
七、各种线程创建方式的对比
创建方式 |
是否有返回值 |
是否支持异常抛出 |
是否支持多继承 |
线程管理难度 |
适用场景 |
继承 Thread 类 |
否 |
否 |
否 |
简单 |
简单任务,不考虑扩展性 |
实现 Runnable 接口 |
否 |
否 |
是 |
简单 |
任务与线程分离的场景 |
实现 Callable 接口 |
是 |
是 |
是 |
中等 |
需要返回值的异步任务 |
CompletableFuture |
是 |
是 |
是 |
中等 |
复杂异步流程编排 |
虚拟线程(Java 19+) |
支持(通过 Future) |
是 |
是 |
简单 |
高并发、I/O 密集型场景 |
3 线程常用方法
3.1 总览
方法 |
说明 |
public void start() |
启动一个新线程,Java虚拟机调用此线程的 run 方法 |
public void run() |
线程启动后调用该方法 |
public void setName(String name) |
给当前线程取名字 |
public void getName() |
获取当前线程的名字 |
public static Thread currentThread() |
获取当前线程对象,代码在哪个线程中执行 |
public static void sleep(long time) |
让当前线程休眠多少毫秒再继续执行 |
public static native void yield() |
提示线程调度器让出当前线程对 CPU 的使用 |
public final int getPriority() |
返回此线程的优先级 |
public final void setPriority(int priority) |
更改此线程的优先级,常用 1 5 10 |
public void interrupt() |
中断这个线程,异常处理机制 |
public static boolean interrupted() |
判断当前线程是否被打断,清除打断标记 |
public boolean isInterrupted() |
判断当前线程是否被打断,不清除打断标记 |
public final void join() |
等待这个线程结束 |
public final void join(long millis) |
等待这个线程死亡 millis 毫秒,0 意味着永远等待 |
public final native boolean isAlive() |
线程是否存活(还没有运行完毕) |
public final void setDaemon(boolean on) |
将此线程标记为守护线程或用户线程 |
3.2 线程创建与启动
1. public void start()
- 作用:启动线程,使线程进入就绪状态(等待 CPU 调度),并自动调用
run()
方法。 - 注意:
- 每个线程只能调用一次
start()
,重复调用会抛出IllegalThreadStateException
。 - 不要直接调用
run()
方法,否则只是普通的方法调用,不会创建新线程。
示例:
Thread t = new Thread(() -> System.out.println("线程执行"));
t.start(); // 正确:启动新线程
// t.start(); // 错误:重复调用
3.3 线程状态控制
1. public static void sleep(long millis)
- 作用:让当前线程暂停执行指定时间(毫秒),进入TIMED_WAITING状态。
- 特点:
- 不释放锁(若持有锁)。
- 可被中断(
interrupt()
),抛出InterruptedException
。
示例:
try {
Thread.sleep(1000); // 暂停1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
2. public static void yield()
- 作用:当前线程让出 CPU 时间片,进入RUNNABLE状态,允许其他线程执行。
- 特点:
- 仅为建议,操作系统可能忽略。
- 不释放锁。
- 与
sleep(0)
类似,但语义更明确。
3. public final void join()
/ join(long millis)
- 作用:等待当前线程终止(或指定时间)。常用于主线程等待子线程完成。
- 示例:
Thread t = new Thread(() -> {
// 子线程任务
});
t.start();
t.join(); // 主线程阻塞,直到t执行完毕
3.4 线程中断
1. public void interrupt()
- 作用:中断线程(设置中断标志位)。
- 若线程处于
sleep()
、wait()
、join()
等阻塞状态,会抛出InterruptedException
并清除标志位。 - 若线程正常运行,仅设置标志位,需通过
isInterrupted()
检测。
2. public boolean isInterrupted()
- 作用:检测线程是否被中断(不清除标志位)。
3. public static boolean interrupted()
- 作用:检测当前线程是否被中断,并清除中断标志位。
正确处理中断的示例:
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
// 执行任务
Thread.sleep(100);
} catch (InterruptedException e) {
// 重置中断标志位,以便退出循环
Thread.currentThread().interrupt();
break;
}
}
});
t.start();
t.interrupt(); // 中断线程
3.5 线程优先级
1. public final void setPriority(int newPriority)
- 作用:设置线程优先级(1-10),默认 5。
- 注意:
- 优先级高的线程理论上更可能被 CPU 调度,但依赖操作系统实现(如 Windows 和 Linux 对优先级的处理不同)。
- 不建议过度依赖优先级控制线程执行顺序。
示例:
Thread t = new Thread();
t.setPriority(Thread.MAX_PRIORITY); // 10
t.setPriority(Thread.MIN_PRIORITY); // 1
t.setPriority(Thread.NORM_PRIORITY); // 5(默认)
3.6 线程状态查询
1. public Thread.State getState()
- 作用:获取线程的当前状态(枚举类型
Thread.State
),包括:
NEW
:新建(未调用start()
)。RUNNABLE
:就绪或运行中。BLOCKED
:等待锁。WAITING
:无限期等待(如wait()
、join()
)。TIMED_WAITING
:限时等待(如sleep(1000)
)。TERMINATED
:已终止。
示例:
Thread t = new Thread();
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE 或 TIMED_WAITING
3.7 线程同步与通信
1. public final void wait()
/ wait(long timeout)
- 作用:当前线程释放对象锁,进入WAITING或TIMED_WAITING状态,直到其他线程调用该对象的
notify()
或notifyAll()
。 - 注意:必须在
synchronized
块中调用,否则抛出IllegalMonitorStateException
。
2. public final void notify()
/ notifyAll()
- 作用:唤醒在该对象上等待的线程(
notify()
随机唤醒一个,notifyAll()
唤醒所有)。 - 注意:必须在
synchronized
块中调用。
生产者 - 消费者示例:
class SharedResource {
private int data;
private boolean available = false;
public synchronized void produce(int value) {
while (available) {
try {
wait(); // 等待消费者取走数据
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
data = value;
available = true;
notifyAll(); // 通知消费者数据已就绪
}
public synchronized int consume() {
while (!available) {
try {
wait(); // 等待生产者生产数据
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
available = false;
notifyAll(); // 通知生产者数据已取走
return data;
}
}
3.8 守护线程
1. public final void setDaemon(boolean on)
- 作用:设置线程为守护线程(
true
)或用户线程(false
)。 - 特点:
- 守护线程在所有用户线程结束后自动终止(如垃圾回收线程)。
- 必须在
start()
前调用,否则抛出IllegalThreadStateException
。
示例:
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("守护线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
// 主线程结束后,守护线程自动终止
3.9 其他常用方法
1. public final String getName()
/ setName(String name)
- 作用:获取 / 设置线程名称,用于调试和日志记录。
2. public final boolean isAlive()
- 作用:判断线程是否处于活动状态(已启动且未终止)。
3. public static Thread currentThread()
- 作用:获取当前执行的线程实例。
3.10 方法对比与注意事项
方法 |
是否静态 |
释放锁 |
中断响应 |
用途场景 |
|
是 |
否 |
是 |
暂停执行 |
|
是 |
否 |
否 |
让出 CPU 时间片 |
|
否 |
否 |
是 |
等待其他线程结束 |
|
否 |
是 |
是 |
线程间通信 |
|
否 |
否 |
否 |
唤醒等待线程 |
|
否 |
否 |
否 |
中断线程 |
4 线程关闭方式
4.1 线程关闭的错误方式
1. public final void stop()
(已弃用)
- 问题:强制终止线程,立即释放所有锁,可能导致数据不一致(如对象处于半初始化状态)。
- 示例:java
Thread t = new Thread(() -> {
synchronized (lock) {
// 修改共享资源
// stop()可能在此处被调用,导致锁释放,资源未完全修改
}
});
t.start();
t.stop(); // 危险!
2. System.exit(int status)
- 问题:终止整个 JVM,所有线程(包括守护线程)都会被强制终止,未释放的资源(如文件句柄)无法清理。
4.2 线程优雅关闭的正确方式
4.2.1 通过标志位控制(推荐)
- 原理:在线程内部设置一个volatile 标志位,外部线程通过修改标志位通知目标线程停止。
- 优点:安全可控,适合长时间运行的任务。
示例代码:
public class FlagControlledThread extends Thread {
private volatile boolean running = true; // 必须为volatile,保证可见性
@Override
public void run() {
while (running) {
try {
// 执行任务
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
break;
}
}
// 清理资源(如关闭文件、释放连接等)
System.out.println("线程正常退出");
}
public void shutdown() {
running = false; // 设置标志位,通知线程停止
}
}
// 使用示例
public static void main(String[] args) throws InterruptedException {
FlagControlledThread thread = new FlagControlledThread();
thread.start();
// 主线程休眠一段时间后请求线程关闭
Thread.sleep(2000);
thread.shutdown(); // 优雅关闭
thread.join(); // 等待线程终止
}
2. 使用中断机制(推荐)
- 原理:通过
interrupt()
设置中断标志,线程在合适的时机检查标志并退出。 - 适用场景:线程处于阻塞状态(如
sleep()
、wait()
、join()
)时也能及时响应。
示例代码:
public class InterruptibleThread extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 执行任务
Thread.sleep(100); // 可能被中断,抛出InterruptedException
} catch (InterruptedException e) {
// 1. 恢复中断状态
Thread.currentThread().interrupt();
// 2. 退出循环
break;
}
}
// 清理资源
System.out.println("线程被中断,正常退出");
}
}
// 使用示例
public static void main(String[] args) throws InterruptedException {
InterruptibleThread thread = new InterruptibleThread();
thread.start();
// 主线程休眠后请求中断
Thread.sleep(2000);
thread.interrupt(); // 发送中断信号
thread.join(); // 等待线程终止
}
3. 结合标志位和中断(最佳实践)
- 原理:同时使用标志位和中断机制,兼顾灵活性和健壮性。
示例代码:
public class HybridShutdownThread extends Thread {
private volatile boolean running = true;
@Override
public void run() {
while (running && !Thread.currentThread().isInterrupted()) {
try {
// 执行任务
Thread.sleep(100);
} catch (InterruptedException e) {
running = false; // 中断后设置标志位
Thread.currentThread().interrupt(); // 恢复中断状态
}
}
// 清理资源
System.out.println("线程优雅关闭");
}
public void shutdown() {
running = false; // 设置标志位
interrupt(); // 中断可能的阻塞操作
}
}
4.2.2 线程池的优雅关闭
1. ExecutorService.shutdown()
- 作用:平缓关闭线程池,不再接受新任务,已提交的任务会继续执行。
2. ExecutorService.shutdownNow()
- 作用:强制关闭线程池,尝试中断正在执行的任务,并返回未执行的任务列表。
- 原理:
- 标记线程池为停止状态:拒绝新任务的提交。
- 中断所有活跃线程:遍历线程池中的工作线程,调用
Thread.interrupt()
。 - 清空任务队列:将等待队列中的任务转移到列表中返回。
- 返回未执行的任务:返回步骤 3 中转移的任务列表。
3. 最佳实践代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolShutdownExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
try {
System.out.println("执行任务: " + taskId);
Thread.sleep(1000);
System.out.println("任务 " + taskId + " 完成");
} catch (InterruptedException e) {
System.out.println("任务 " + taskId + " 被中断");
Thread.currentThread().interrupt();
}
});
}
// 优雅关闭线程池
executor.shutdown(); // 停止接受新任务
try {
// 等待已提交的任务执行完毕(最多等待30秒)
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
// 超时未完成,强制关闭
executor.shutdownNow();
// 再次等待剩余任务中断(最多等待10秒)
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
System.err.println("线程池关闭失败");
}
}
} catch (InterruptedException e) {
// 主线程被中断,强制关闭
executor.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("线程池已关闭");
}
}
4.3 资源清理的最佳实践
在线程关闭时,需确保释放所有持有的资源(如文件、网络连接、数据库连接等),推荐使用:
- try-with-resources:自动关闭实现了
AutoCloseable
接口的资源。
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
// 使用资源
} catch (IOException e) {
e.printStackTrace();
}
// 资源自动关闭
}
- finally 块:确保无论是否发生异常,资源都能被关闭。
@Override
public void run() {
Connection conn = null;
try {
conn = getDatabaseConnection();
// 使用连接执行操作
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close(); // 手动关闭资源
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
4.4 总结
线程的优雅关闭需遵循以下原则:
- 避免强制终止:不使用
stop()
、destroy()
等方法。 - 协作式关闭:通过标志位或中断机制通知线程自行终止。
- 资源清理:使用
try-with-resources
或finally
确保资源释放。 - 线程池关闭:按顺序调用
shutdown()
和awaitTermination()
。
通过以上方法,可以确保线程安全退出,避免数据不一致和资源泄漏,提高系统的稳定性和可靠性。
5 线程的状态
5.1 六种状态(Thread.State 枚举)
5.1.1 NEW(新建)
- 含义:线程已被创建(通过
new Thread()
),但尚未调用start()
方法。 - 示例:
Thread t = new Thread(() -> System.out.println("线程执行"));
// 此时t处于NEW状态
- 特点:
- 线程对象已创建,但尚未分配系统资源(如操作系统线程)。
- 只能调用
start()
方法,其他操作(如interrupt()
)会抛出异常。
5.1.2 RUNNABLE(可运行)
- 含义:线程已启动(调用
start()
),正在 JVM 中运行或等待 CPU 时间片。 - 细分状态:
- Running:线程正在 CPU 上执行。
- Ready:线程处于就绪队列,等待操作系统调度。
- 示例:
t.start(); // 调用start()后,t进入RUNNABLE状态
- 特点:
- 线程可能正在执行,也可能在等待 CPU 资源。
- Java 将操作系统层面的
Running
和Ready
状态统一抽象为RUNNABLE
。
5.1.3 BLOCKED(阻塞)
- 含义:线程正在等待获取锁(如
synchronized
块或方法)。 - 触发场景:
- 线程尝试进入
synchronized
代码块,但锁已被其他线程持有。 - 锁被释放后,线程从
BLOCKED
变为RUNNABLE
,重新竞争锁。
- 示例:
public class BlockedExample {
private static final Object LOCK = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (LOCK) {
// 持有锁,长时间运行
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (LOCK) { // t2在此处阻塞,进入BLOCKED状态
System.out.println("t2获取到锁");
}
});
t1.start();
t2.start();
}
}
5.1.4 WAITING(无限期等待)
- 含义:线程等待另一个线程执行特定操作,需通过其他线程显式唤醒。
- 触发方法:
Object.wait()
:释放对象锁,进入等待状态,直到其他线程调用notify()
/notifyAll()
。Thread.join()
:等待目标线程终止。LockSupport.park()
:等待许可(permit)。
- 示例:
public class WaitingExample {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
synchronized (this) {
try {
wait(); // 线程t进入WAITING状态
} catch (InterruptedException e) {}
}
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // 输出WAITING
synchronized (this) {
notify(); // 唤醒线程t
}
}
}
5.1.5 TIMED_WAITING(限时等待)
- 含义:线程在指定时间内等待,超时后自动恢复。
- 触发方法:
Thread.sleep(long millis)
:线程暂停执行指定时间。Object.wait(long timeout)
:释放锁,等待指定时间或被唤醒。Thread.join(long millis)
:等待目标线程终止,最多等待指定时间。LockSupport.parkNanos(long nanos)
/parkUntil(long deadline)
:限时等待许可。
- 示例:
public class TimedWaitingExample {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(2000); // 线程t进入TIMED_WAITING状态
} catch (InterruptedException e) {}
});
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // 输出TIMED_WAITING
}
}
5.1.6 TERMINATED(终止)
- 含义:线程执行完毕或因异常终止,生命周期结束。
- 触发条件:
run()
方法正常返回。run()
方法抛出未捕获的异常。
- 示例:
public class TerminatedExample {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("线程执行");
});
t.start();
Thread.sleep(100); // 等待线程t执行完毕
System.out.println(t.getState()); // 输出TERMINATED
}
}