线程基础
约 4480 字大约 15 分钟
2026-04-21
本文全面覆盖 Java 线程核心基础知识,包括生命周期、创建方式、守护线程、调度优先级、线程组、ThreadLocal、中断机制以及线程协作方法。
1. 线程生命周期(Thread Lifecycle)
核心概念
Java 线程在生命周期中会经历 6 种状态,定义在 Thread.State 枚举中:
| 状态 | 说明 |
|---|---|
| NEW | 线程对象已创建,但尚未调用 start() |
| RUNNABLE | 调用 start() 后,线程处于可运行状态(包含操作系统的 Ready 和 Running) |
| BLOCKED | 线程等待获取 synchronized 锁(锁池) |
| WAITING | 线程无限期等待(等待池),等待其他线程显式唤醒 |
| TIMED_WAITING | 线程在指定时间内等待,超时后自动返回 |
| TERMINATED | 线程执行完毕或因异常退出 |
状态转换图
关键 API 签名
// 获取线程当前状态
public State getState()
// 线程是否存活(RUNNABLE / BLOCKED / WAITING / TIMED_WAITING 均为 true)
public final boolean isAlive()代码示例
Thread t = new Thread(() -> System.out.println("running"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE
t.join();
System.out.println(t.getState()); // TERMINATED常见陷阱
- RUNNABLE 包含了操作系统的 Ready 和 Running 两种子状态,Java 层面不区分
- 调用
start()后线程并不会立即执行,而是进入 RUNNABLE 状态等待 CPU 调度 - 对已启动的线程重复调用
start()会抛出IllegalThreadStateException BLOCKED仅针对 synchronized 锁等待,Lock 框架的等待对应WAITING或TIMED_WAITING
最佳实践
- 通过
getState()进行调试和监控,不要在生产逻辑中依赖线程状态做业务判断 - 始终检查
isAlive()而不是getState() != TERMINATED来判断线程是否还在运行
2. 线程创建方式
方式一:继承 Thread 类
核心概念
- Thread 类本身实现了
Runnable接口 - 继承 Thread 后重写
run()方法定义线程执行体 - 由于 Java 单继承限制,该方式灵活性较低
- 多个线程之间无法共享线程类的实例变量
关键 API 签名
public class Thread implements Runnable {
public Thread();
public Thread(String name);
public Thread(Runnable target);
public Thread(Runnable target, String name);
public void start(); // 启动线程,JVM 调用 run()
public void run(); // 线程执行体
}代码示例
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(getName() + " is running");
}
}
// 使用
Thread t = new MyThread();
t.setName("worker-1");
t.start();方式二:实现 Runnable 接口
核心概念
Runnable是函数式接口(@FunctionalInterface),只有一个run()方法- 将任务(Runnable)与线程(Thread)解耦,更符合单一职责原则
- 多个线程可以共享同一个 Runnable 对象的实例变量
关键 API 签名
@FunctionalInterface
public interface Runnable {
void run();
}代码示例
Runnable task = () -> System.out.println(Thread.currentThread().getName() + " is running");
new Thread(task, "worker-1").start();
// 多线程共享同一个 target
Runnable sharedTask = () -> { /* 访问共享资源 */ };
new Thread(sharedTask, "A").start();
new Thread(sharedTask, "B").start();方式三:实现 Callable + FutureTask
核心概念
Callable<V>是有返回值的任务接口,call()方法可以抛出受检异常FutureTask<V>同时实现了Runnable和Future<V>,可以作为 Thread 的 target- 适合需要获取线程执行结果的场景
关键 API 签名
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
public class FutureTask<V> implements RunnableFuture<V> {
public FutureTask(Callable<V> callable);
public FutureTask(Runnable runnable, V result);
}
public interface Future<V> {
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
}代码示例
Callable<String> task = () -> {
Thread.sleep(1000);
return "result from " + Thread.currentThread().getName();
};
FutureTask<String> futureTask = new FutureTask<>(task);
new Thread(futureTask, "callable-worker").start();
// get() 会阻塞直到线程返回结果
String result = futureTask.get(2, TimeUnit.SECONDS);
System.out.println(result);三种方式对比
| 特性 | extends Thread | implements Runnable | implements Callable |
|---|---|---|---|
| 返回值 | 无 | 无 | 有 (V call()) |
| 异常 | 无受检异常 | 无受检异常 | 可抛出受检异常 |
| 继承限制 | 单继承 | 无 | 无 |
| 共享变量 | 不支持 | 支持(共享 target) | 支持(共享 target) |
| 获取当前线程 | this | Thread.currentThread() | Thread.currentThread() |
常见陷阱
- 直接调用
run()而非start():这会在当前线程中同步执行,不会创建新线程 FutureTask.get()是阻塞调用,如果不设置超时可能导致主线程永远阻塞- Callable 的
call()抛出的异常会被包装为ExecutionException
最佳实践
- 优先使用
Runnable或Callable接口,避免继承 Thread - 实际生产中应使用线程池(
ExecutorService)管理线程,而非手动new Thread()
3. 守护线程(Daemon Thread)
核心概念
- 守护线程(Daemon Thread)为其他线程(用户线程)提供服务,如 GC 线程
- 用户线程(User Thread)是应用程序的前台工作线程
- 当所有用户线程结束后,JVM 会退出,所有守护线程随之自动销毁
- 守护线程中创建的子线程默认也是守护线程
关键 API 签名
public final void setDaemon(boolean on) // 必须在 start() 前调用
public final boolean isDaemon()代码示例
Thread daemon = new Thread(() -> {
while (true) {
System.out.println("daemon working...");
try { Thread.sleep(500); } catch (InterruptedException e) { break; }
}
});
daemon.setDaemon(true); // 必须在 start() 之前设置
daemon.start();
// 主线程(用户线程)结束后,daemon 线程会自动终止
Thread.sleep(2000);
System.out.println("main thread exits");常见陷阱
- 在
start()之后调用setDaemon(true)会抛出IllegalThreadStateException - 守护线程的
finally块不一定会执行(JVM 退出时不等待守护线程完成),因此不要在守护线程中执行需要善后的 I/O 操作 - 守护线程中不要操作需要持久化的资源(文件、数据库等),因为随时可能被强制终止
最佳实践
- 守护线程适合用于后台监控、日志采集、心跳检测等辅助性任务
- 线程池中的线程默认是用户线程;通过自定义
ThreadFactory可以创建守护线程
4. 线程调度与优先级
核心概念
- Java 采用抢占式调度(Preemptive Scheduling),线程的执行顺序由 JVM 和操作系统共同决定
- 线程优先级是一个提示性参数(1~10),不保证执行顺序
- 优先级高的线程仅表示获得 CPU 时间片的概率更大,不意味着一定会先执行
关键 API 签名
// 优先级常量
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5; // 默认优先级
public static final int MAX_PRIORITY = 10;
// 实例方法
public final int getPriority()
public final void setPriority(int newPriority)代码示例
Thread high = new Thread(() -> System.out.println("high priority"));
Thread low = new Thread(() -> System.out.println("low priority"));
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
low.start();
high.start();
// 输出顺序不确定,但 high 优先获得 CPU 的概率更大常见陷阱
- 不同操作系统对线程优先级的映射不同,Windows 有 7 个级别,Linux 可能完全忽略优先级
- 不要依赖优先级来保证程序正确性,它只是一种性能优化手段
- 子线程默认继承父线程的优先级
最佳实践
- 绝大多数情况下使用默认优先级(
NORM_PRIORITY)即可 - 如果确实需要调整,优先级差距要足够大(如 1 vs 10)才有明显效果
5. 线程组(ThreadGroup)
核心概念
ThreadGroup用于对一组线程进行集中管理(批量操作)- 每个线程必定属于一个线程组,默认与创建它的父线程同组
- 主线程的线程组名为
"main" - 在现代 Java 开发中,线程组的使用已逐渐减少,推荐使用线程池替代
关键 API 签名
// 构造器
public ThreadGroup(String name)
public ThreadGroup(ThreadGroup parent, String name)
// 常用方法
public int activeCount() // 估算活跃线程数
public int enumerate(Thread[] list) // 将线程复制到数组
public String getName()
public ThreadGroup getParent()
public void interrupt() // 中断组内所有线程
public boolean isDaemon()
public void setDaemon(boolean daemon)
public void uncaughtException(Thread t, Throwable e) // 处理未捕获异常代码示例
ThreadGroup group = new ThreadGroup("my-group");
Thread t1 = new Thread(group, () -> System.out.println("task-1"), "worker-1");
Thread t2 = new Thread(group, () -> System.out.println("task-2"), "worker-2");
t1.start();
t2.start();
System.out.println("活跃线程数: " + group.activeCount());
// 批量中断组内所有线程
group.interrupt();常见陷阱
activeCount()返回的是估算值,可能不准确- 线程组内的
destroy()方法只能销毁没有任何活跃线程的组 - 线程组本身已经是一种过时的 API,
java.util.concurrent包提供了更好的替代方案
最佳实践
- 新项目中优先使用
ExecutorService线程池,而非 ThreadGroup - 如果需要统一处理未捕获异常,使用
Thread.setUncaughtExceptionHandler()更灵活
6. ThreadLocal 与 InheritableThreadLocal
ThreadLocal 核心概念
ThreadLocal为每个线程维护一个独立的变量副本,实现线程隔离- 内部通过
ThreadLocalMap(定义在 Thread 类中)存储数据,key 为 ThreadLocal 实例,value 为线程的变量副本 - 常用于存储线程上下文信息(用户身份、数据库连接、日期格式化器等)
关键 API 签名
public class ThreadLocal<T> {
public ThreadLocal();
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier);
public T get();
public void set(T value);
public void remove();
protected T initialValue();
}代码示例
// 方式一:覆写 initialValue
private static final ThreadLocal<SimpleDateFormat> sdfHolder =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
// 方式二:使用 withInitial(推荐)
private static final ThreadLocal<SimpleDateFormat> sdfHolder =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
// 使用
SimpleDateFormat sdf = sdfHolder.get(); // 每个线程获取独立的实例
sdfHolder.remove(); // 用完后清理,防止内存泄漏InheritableThreadLocal 核心概念
InheritableThreadLocal是ThreadLocal的子类- 子线程创建时会自动继承父线程中
InheritableThreadLocal的值 - 仅在子线程创建时复制一次,之后父子线程的值相互独立
代码示例
private static final InheritableThreadLocal<String> context = new InheritableThreadLocal<>();
public static void main(String[] args) {
context.set("parent-value");
Thread child = new Thread(() -> {
System.out.println("子线程获取: " + context.get()); // "parent-value"
context.set("child-value");
System.out.println("子线程修改后: " + context.get()); // "child-value"
});
child.start();
child.join();
System.out.println("父线程值不变: " + context.get()); // "parent-value"
}常见陷阱
- 内存泄漏:ThreadLocalMap 的 key 是弱引用(ThreadLocal 实例),但 value 是强引用。如果线程长期存活(如线程池),value 不会被 GC 回收。必须在 finally 块中调用
remove() - 线程池场景:线程复用时,上一次设置的 ThreadLocal 值会残留,导致数据串线。必须在任务执行前后进行
set()/remove() InheritableThreadLocal在线程池场景下不可靠,因为线程池中的线程是复用的而非新建的,不会触发继承。推荐使用阿里开源的TransmittableThreadLocal(TTL)解决
最佳实践
- ThreadLocal 变量声明为
private static final - 始终在使用后调用
remove(),最好放在try-finally中 - 在线程池场景下,使用
try-finally确保每次任务执行后清理 ThreadLocal
// 线程池中使用 ThreadLocal 的标准模式
executor.submit(() -> {
try {
context.set(userInfo);
doBusiness();
} finally {
context.remove(); // 必须!
}
});7. 线程中断机制
核心概念
- Java 没有安全的方式强制停止线程,因此采用协作式中断
- 每个线程都有一个
interrupted标志位 - 调用
interrupt()会设置该标志位,但不会强制终止线程 - 线程通过主动检查中断标志来决定是否停止
关键 API 签名
// 实例方法 —— 设置中断标志
public void interrupt()
// 实例方法 —— 检查中断标志(不清除)
public boolean isInterrupted()
// 静态方法 —— 检查并清除当前线程的中断标志
public static boolean interrupted()中断行为分类
| 线程当前状态 | 调用 interrupt() 的效果 |
|---|---|
| RUNNABLE(正常运行) | 仅设置中断标志位为 true,线程继续运行 |
| WAITING / TIMED_WAITING(wait/sleep/join) | 清除中断标志,抛出 InterruptedException |
| BLOCKED(等待 synchronized 锁) | 仅设置中断标志位,线程继续等待锁 |
| Lock.lock() 等待中 | 仅设置中断标志位(需用 lockInterruptibly() 才能响应中断) |
代码示例
方式一:主动检查中断标志
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
System.out.println("working...");
}
System.out.println("线程正常退出");
});
t.start();
Thread.sleep(100);
t.interrupt(); // 请求中断方式二:捕获 InterruptedException
Thread t = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// InterruptedException 会清除中断标志!
System.out.println("被中断,中断标志: " + Thread.currentThread().isInterrupted()); // false
// 如果需要保留中断状态,应重新设置:
// Thread.currentThread().interrupt();
}
});
t.start();
t.interrupt();常见陷阱
InterruptedException被捕获后中断标志会被自动清除,不要吞掉异常而不做任何处理Thread.interrupted()是静态方法,检查并清除当前线程的中断标志;isInterrupted()是实例方法,不会清除标志- 不要在生产代码中使用已废弃的
stop()、suspend()、resume()方法
最佳实践
// 正确处理 InterruptedException 的方式
// 方式一:向上抛出
public void run() throws InterruptedException {
// ... 业务代码
}
// 方式二:捕获后恢复中断状态
catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断标志
// 或者 break/return 退出循环
}8. Thread.join()、Thread.yield()、Thread.sleep()
Thread.sleep()
核心概念
- 让当前线程暂停执行指定的毫秒数,进入
TIMED_WAITING状态 - 不释放任何锁(synchronized 锁、Lock 锁都不会释放)
- 到期后线程回到 RUNNABLE 状态,等待 CPU 调度
关键 API 签名
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException;代码示例
// 简易重试机制
for (int i = 0; i < 3; i++) {
if (tryConnect()) break;
Thread.sleep(1000 * (i + 1)); // 递增等待
}
// Java 9+ 支持 Duration
Thread.sleep(Duration.ofSeconds(2));Thread.join()
核心概念
- 让当前线程等待目标线程执行完毕
- 底层通过
wait()实现(在目标线程对象上等待) - 当目标线程终止时,会自动调用
notifyAll()唤醒所有等待的线程
关键 API 签名
public final void join() throws InterruptedException // 无限等待
public final synchronized void join(long millis) throws InterruptedException // 限时等待
public final synchronized void join(long millis, int nanos) throws InterruptedException代码示例
Thread t1 = new Thread(() -> { /* 耗时任务 */ });
Thread t2 = new Thread(() -> { /* 耗时任务 */ });
t1.start();
t2.start();
// 等待两个线程都完成
t1.join();
t2.join();
System.out.println("所有任务完成");Thread.yield()
核心概念
- 提示调度器当前线程愿意让出 CPU 时间片,进入 RUNNABLE 状态(Ready)
- 仅仅是提示,调度器可以忽略
- 让出后,只有优先级 >= 当前线程的其他线程才有机会获得 CPU
关键 API 签名
public static native void yield();代码示例
// 在循环中适度让步,减少 CPU 空转
while (!done) {
if (hasWork()) {
process();
} else {
Thread.yield(); // 没有工作时让出 CPU
}
}三者对比
| 特性 | sleep() | join() | yield() |
|---|---|---|---|
| 作用 | 暂停当前线程 | 等待目标线程完成 | 让出 CPU 时间片 |
| 目标线程 | 当前线程 | 其他线程 | 当前线程 |
| 释放锁 | 不释放 | 不释放(底层是 wait,会在目标线程对象上等待) | 不释放 |
| 进入状态 | TIMED_WAITING | WAITING / TIMED_WAITING | RUNNABLE(Ready) |
| 精确性 | 相对精确 | 依赖目标线程 | 完全不可控 |
常见陷阱
sleep(0)不会真正休眠,但会触发线程调度(类似 yield)join()在目标线程尚未启动时立即返回yield()在实际应用中很少使用,可读性差且效果不可控
最佳实践
- 使用
TimeUnit.MILLISECONDS.sleep(500)替代Thread.sleep(500),可读性更好 join()应始终设置超时时间,防止死等- 优先使用
java.util.concurrent包中的高级工具(如CountDownLatch、CompletableFuture)替代手动的join()和sleep()
9. Object.wait()、Object.notify()、Object.notifyAll()
核心概念
- 这三个方法是
Object类的final native方法,任何对象都可以作为同步监视器 - 必须在 synchronized 块内调用,且调用者必须是当前持有锁的对象(即同步监视器),否则抛出
IllegalMonitorStateException wait()会释放锁并进入等待池;notify()/notifyAll()唤醒等待池中的线程进入锁池重新竞争锁
关键 API 签名
// Object 类方法
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException;
public final native void notify();
public final native void notifyAll();等待/通知机制的标准范式
// ====== 消费者(等待方) ======
synchronized (lock) {
while (!condition) { // 必须用 while,不能用 if(防止虚假唤醒)
lock.wait(); // 释放锁,进入等待
}
// 条件满足,执行操作
doSomething();
}
// ====== 生产者(通知方) ======
synchronized (lock) {
changeState(); // 改变条件
lock.notifyAll(); // 唤醒等待线程(推荐 notifyAll)
}经典示例:生产者-消费者
public class BoundedBuffer<T> {
private final Object[] items;
private int putIdx, takeIdx, count;
private final Object lock = new Object();
public BoundedBuffer(int capacity) {
items = new Object[capacity];
}
@SuppressWarnings("unchecked")
public T take() throws InterruptedException {
synchronized (lock) {
while (count == 0) {
lock.wait(); // 缓冲区空,等待
}
T item = (T) items[takeIdx];
takeIdx = (takeIdx + 1) % items.length;
count--;
lock.notifyAll(); // 通知生产者可以放入
return item;
}
}
public void put(T item) throws InterruptedException {
synchronized (lock) {
while (count == items.length) {
lock.wait(); // 缓冲区满,等待
}
items[putIdx] = item;
putIdx = (putIdx + 1) % items.length;
count++;
lock.notifyAll(); // 通知消费者可以取出
}
}
}wait() 的释放锁规则
| 操作 | 是否释放 synchronized 锁 |
|---|---|
wait() | 释放(进入等待池) |
sleep() | 不释放 |
yield() | 不释放 |
suspend() (已废弃) | 不释放 |
常见陷阱
- 虚假唤醒(Spurious Wakeup):线程可能在没有被
notify()的情况下从wait()中返回,因此必须使用while循环检查条件,而不是if - 在错误的锁对象上调用
wait()/notify()会抛出IllegalMonitorStateException notify()唤醒的是等待池中的任意一个线程(不保证公平性),可能导致某些线程永远得不到唤醒(信号丢失问题)wait(0)等同于wait()(无限期等待)
最佳实践
- 始终使用
notifyAll()而非notify(),除非你明确知道只有一个等待线程且条件精确匹配 - 始终使用
while循环保护wait()调用 - 推荐使用
java.util.concurrent包中的高级工具替代手写 wait/notify:BlockingQueue替代生产者-消费者模式Condition接口(配合Lock)替代wait()/notify()CountDownLatch/CyclicBarrier替代线程间的等待/通知
总结:线程协作工具选型指南
| 需求场景 | 推荐方案 |
|---|---|
| 线程间简单等待/通知 | synchronized + wait() / notifyAll() |
| 精确的线程间等待/通知 | Lock + Condition |
| 生产者-消费者模式 | BlockingQueue |
| 等待一组线程完成 | CountDownLatch 或 CompletableFuture |
| 线程间屏障同步 | CyclicBarrier |
| 限流 / 并发数控制 | Semaphore |
| 异步计算 | CompletableFuture |
| 定时/周期任务 | ScheduledExecutorService |
参考链接:
更新日志
2026/5/5 18:19
查看所有更新日志
fde06-docs: 添加 doc 技术文档于
