什么是线程?
线程(Thread)是程序执行的最小单元,也是 CPU 调度的基本单位。 一个进程(Process)可以包含多个线程,这些线程共享进程的内存空间(堆、方法区), 但每个线程有自己独立的程序计数器(PC)、虚拟机栈(Stack) 和本地方法栈。
创建线程的方式
方式一:继承 Thread 类
public class MyThread extends Thread {
private final String taskName;
public MyThread(String taskName) {
super(taskName); // 设置线程名称
this.taskName = taskName;
}
@Override
public void run() {
System.out.println("任务 " + taskName + " 在线程 " + getName() + " 中执行");
}
}
// 使用
Thread t = new MyThread("下载图片");
t.start(); // 启动线程(不能调用 run()!)
start() 创建新线程并执行 run();直接调用 run()
则在当前线程中同步执行,与普通方法调用无异,不会创建新线程。这是初学者最常犯的错误之一。
方式二:实现 Runnable 接口(推荐)
// Runnable 是函数式接口,可用 lambda 表达式
Runnable task = () -> {
System.out.println("执行任务,线程:" + Thread.currentThread().getName());
};
Thread t = new Thread(task, "worker-1");
t.start();
// 更简洁的写法
new Thread(() -> System.out.println("Hello from thread"), "my-thread").start();
推荐 Runnable 而非继承 Thread 的原因:Java 是单继承,继承 Thread
就用掉了唯一的父类槽位;而实现接口没有这个限制,更灵活,也更符合「组合优于继承」的设计原则。
方式三:实现 Callable + Future(有返回值)
import java.util.concurrent.*;
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 42; // 可以有返回值,Runnable 不行
};
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);
// 阻塞等待结果
Integer result = future.get(); // 返回 42
System.out.println("结果:" + result);
executor.shutdown();
线程的生命周期
Java 线程有 6 种状态,定义在 Thread.State 枚举中:
核心 Thread API
sleep():让当前线程休眠
// Thread.sleep() —— 使当前线程休眠指定时间
try {
Thread.sleep(1000); // 休眠 1000 毫秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断标志!
// 处理中断逻辑
}
// Java 9+ 推荐使用 TimeUnit(可读性更好)
TimeUnit.SECONDS.sleep(1);
TimeUnit.MILLISECONDS.sleep(500);
sleep() 会释放 CPU 时间片,但不会释放已持有的锁。
这与 wait()(释放锁)形成对比。
join():等待另一个线程完成
Thread downloader = new Thread(() -> {
System.out.println("开始下载...");
try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
System.out.println("下载完成!");
});
downloader.start();
System.out.println("等待下载...");
downloader.join(); // 主线程阻塞,直到 downloader 线程终止
System.out.println("所有任务完成");
// join(timeout) 版本:最多等待指定毫秒数
downloader.join(3000); // 最多等 3 秒
interrupt():中断线程
Java 线程中断采用协作式中断模型:调用 interrupt() 只是
设置目标线程的中断标志位,并不强制终止线程。被中断的线程需要自己检查
中断状态并决定如何响应。
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) { // 主动检查中断标志
try {
System.out.println("工作中...");
Thread.sleep(500); // 阻塞时收到中断 → 抛出 InterruptedException
} catch (InterruptedException e) {
System.out.println("收到中断信号,清理后退出");
Thread.currentThread().interrupt(); // 重新标记中断(捕获后中断标志被清除)
break;
}
}
System.out.println("线程正常退出");
});
worker.start();
Thread.sleep(1500);
worker.interrupt(); // 请求中断
Thread.stop() 会立即终止线程并强制释放所有锁,可能导致数据结构处于不一致状态。
Thread.suspend()/resume() 容易导致死锁。
两者均已标记为 @Deprecated,请永远不要使用。
正确的做法是使用协作式中断(interrupt() + 主动检查)。
线程优先级与守护线程
Thread t = new Thread(() -> System.out.println("task"));
// 线程优先级(1-10,默认5)
t.setPriority(Thread.MAX_PRIORITY); // 10
t.setPriority(Thread.MIN_PRIORITY); // 1
t.setPriority(Thread.NORM_PRIORITY); // 5(默认)
// 注意:优先级只是"建议",实际调度由 OS 决定,不同平台行为差异大
// 守护线程(Daemon Thread)
// 守护线程在所有非守护线程结束后自动退出(JVM 随之退出)
Thread daemon = new Thread(() -> {
while (true) {
// 后台监控任务
}
});
daemon.setDaemon(true); // 必须在 start() 之前设置
daemon.start();
ThreadLocal:线程本地变量
ThreadLocal 为每个线程提供独立的变量副本,彻底消除共享,从根本上避免并发冲突。
常见用途:数据库连接、用户会话信息、事务上下文传递。
public class UserContext {
// 每个线程都有自己独立的 userId 副本
private static final ThreadLocal<String> USER_ID =
ThreadLocal.withInitial(() -> "anonymous");
public static void setUserId(String id) { USER_ID.set(id); }
public static String getUserId() { return USER_ID.get(); }
// 非常重要:用完必须清除,防止线程池中的线程复用导致数据污染
public static void clear() { USER_ID.remove(); }
}
// Web 请求处理示例(Filter 中设置,Controller 中使用)
UserContext.setUserId("user-123");
try {
processRequest(); // 整个调用链都能通过 UserContext.getUserId() 获取
} finally {
UserContext.clear(); // 必须在 finally 中清除!
}
在线程池环境中,线程会被复用。如果没有调用 remove() 清除 ThreadLocal 值,
下一个使用该线程的请求可能读到上一个请求残留的数据。更严重的是,若 ThreadLocal key 的强引用
被回收后,value 仍挂在 ThreadLocalMap 中无法 GC,造成内存泄漏。
规范:每次使用完 ThreadLocal 务必在 finally 块中调用 remove()。
Java 21 虚拟线程(Virtual Threads)入门
平台线程的瓶颈
传统的 Java 平台线程(Platform Thread)与操作系统线程 1:1 对应。创建一个平台线程需要在 OS 层 分配约 1MB 的栈空间,线程切换涉及内核态/用户态切换,开销较大。 通常一个 JVM 进程最多支持数千个平台线程——这在处理大量 I/O 密集型并发请求时会成为瓶颈。
传统解法是使用异步/响应式编程(Reactive Programming),但这带来了「回调地狱」或复杂的 API, 破坏了代码的线性可读性。
虚拟线程的解决方案
Java 21(JEP 444)正式发布虚拟线程(Virtual Threads),由 JVM 管理, 运行在一小批平台线程(称为载体线程 Carrier Thread)之上。 虚拟线程在等待 I/O 时会自动卸载(unmount)离开载体线程, 让载体线程去执行其他虚拟线程;I/O 就绪后再重新挂载(mount)继续执行。
创建虚拟线程
// 方式一:Thread.ofVirtual()(推荐)
Thread vt = Thread.ofVirtual()
.name("vthread-1")
.start(() -> System.out.println("虚拟线程执行"));
// 方式二:工厂方法
Thread.Builder.OfVirtual builder = Thread.ofVirtual().name("task-", 0);
Thread vt2 = builder.start(() -> {
System.out.println("任务2,线程:" + Thread.currentThread());
});
// 方式三:使用虚拟线程执行器(适合提交大量任务)
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1_000_000; i++) {
final int taskId = i;
executor.submit(() -> {
// 模拟 I/O 操作(虚拟线程在此处卸载,不阻塞载体线程)
Thread.sleep(Duration.ofMillis(100));
return "task-" + taskId;
});
}
} // try-with-resources 自动关闭并等待所有任务完成
检测当前线程是否为虚拟线程
Thread t = Thread.currentThread();
System.out.println("是否为虚拟线程:" + t.isVirtual()); // Java 21 新方法
System.out.println("线程名:" + t.getName());
System.out.println("线程描述:" + t); // VirtualThread[#21]/runnable@ForkJoinPool...
虚拟线程的关键特性
优势
- 创建成本极低(约几KB内存 vs 平台线程1MB)
- 可创建百万级虚拟线程
- 保持同步代码的可读性(无需 async/await)
- 完全兼容现有 Thread API
- I/O 阻塞时自动挂起,不浪费 OS 线程
注意事项
- CPU 密集型任务不适合(不能改善 CPU 利用率)
- synchronized 块会导致 pinning(固定载体线程)
- 不应使用线程池管理虚拟线程(每任务一虚拟线程)
- ThreadLocal 仍可用,但注意内存(百万线程×ThreadLocal)
- 栈变量仍在堆中,GC 压力可能增加
// 性能对比演示:1万个并发 HTTP 请求
import java.net.http.*;
import java.net.*;
// 传统方式:需要维护连接池,限制并发数
ExecutorService pool = Executors.newFixedThreadPool(200); // 最多200个并发
// 虚拟线程方式:每请求一虚拟线程,轻松支持1万并发
HttpClient client = HttpClient.newBuilder()
.executor(Executors.newVirtualThreadPerTaskExecutor())
.build();
try (ExecutorService vPool = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
vPool.submit(() -> {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.build();
return client.send(req, HttpResponse.BodyHandlers.ofString());
});
}
}
虚拟线程专为 I/O 密集型任务设计(数据库查询、HTTP 调用、文件读写等)。
不要用线程池来「管理」虚拟线程(失去了虚拟线程的轻量级优势),直接使用
Executors.newVirtualThreadPerTaskExecutor() 或 Thread.ofVirtual().start()。
CPU 密集型任务仍应使用平台线程或 ForkJoinPool。
本章小结
- 线程是 CPU 调度的基本单位,Java 提供 Thread、Runnable、Callable 三种创建方式,推荐 Runnable + Lambda。
- 线程有 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
- 核心 API:start()(不是 run())、sleep()(不释放锁)、join()(等待完成)、interrupt()(协作式中断)。
- ThreadLocal 通过线程隔离避免共享,但线程池环境下必须手动 remove()。
- 虚拟线程(Java 21 正式发布)是轻量级线程,适合 I/O 密集型场景,可创建百万级而不耗尽 OS 资源。