引言
各位开发者好!在上一篇文章中,我们详细介绍了 Java 多线程的四种创建方式。今天,我们将深入探讨线程的生命周期和基础操作方法,这些知识对于理解多线程程序的行为和调试线程问题至关重要。
很多初学者在多线程编程中遇到的困惑,往往源于对线程状态转换和控制方法的理解不足。比如:为什么我的线程没有执行?为什么线程无法停止?如何优雅地结束一个线程?今天,我们就来一一解答这些问题。
一、线程的六大状态详解
Java 中的 Thread 类定义了线程的六种状态,这些状态定义在 Thread 类的内部枚举 State 中:
java
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
下面我们详细解析每一种状态及其转换过程:
1. NEW(新建)
当你创建一个 Thread 对象但还没有调用 start()方法时,线程处于 NEW 状态。
java
Thread thread = new Thread(() -> {
System.out.println("线程任务执行");
});
thread.setName("worker-thread"); // 给线程起一个有意义的名称
System.out.println("创建后线程状态:" + thread.getState()); // 输出: NEW
2. RUNNABLE(可运行)
调用 start()方法后,线程进入 RUNNABLE 状态。在 Java 中,RUNNABLE 状态包含了操作系统层面的"就绪"和"运行中"两个状态:
- 就绪:线程已经准备好运行,但等待 CPU 分配时间片
- 运行中:线程正在 CPU 上执行
重要说明:Java 中的 RUNNABLE 状态是一个复合状态,不区分线程是"就绪"还是"正在运行",这与操作系统的线程状态模型不同。即使线程获得了 CPU 时间片正在执行,在 Java API 看来它仍然是 RUNNABLE 状态,无法通过 Thread.getState()区分线程是否正在 CPU 上执行。
java
thread.start();
System.out.println("启动后线程状态:" + thread.getState()); // 输出: RUNNABLE
3. BLOCKED(阻塞)
线程被阻塞,等待获取一个 synchronized 内置锁(也称 monitor 锁)。当线程尝试进入一个 synchronized 块/方法,但该锁被其他线程持有时,就会进入这个状态。
java
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(3000); // 持有锁3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.setName("lock-holder");
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取到了锁");
}
});
thread2.setName("lock-waiter");
thread1.start(); // 先启动线程1
Thread.sleep(100); // 确保线程1先获取到锁
thread2.start(); // 再启动线程2
Thread.sleep(100); // 给线程2一点时间尝试获取锁
System.out.println("线程2状态:" + thread2.getState()); // 输出: BLOCKED
4. WAITING(等待)
线程进入无限期等待状态,需要其他线程执行特定操作后才能继续。 主要由以下方法导致:
- Object.wait()
- Thread.join()
- LockSupport.park()
特别说明:Object.wait()方法会释放持有的 monitor 锁(也就是 synchronized 锁),而 LockSupport.park()不会释放任何锁,这是一个重要区别。
性能考虑:在高并发环境中,过多线程进入 WAITING 状态可能导致系统资源浪费。建议使用带超时参数的 wait(timeout),避免由于通知丢失导致线程永久等待。
java
Object lock = new Object();
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 进入WAITING状态,同时释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
waitingThread.setName("waiting-thread");
waitingThread.start();
Thread.sleep(100); // 确保waitingThread进入等待状态
System.out.println("等待中的线程状态:" + waitingThread.getState()); // 输出: WAITING
5. TIMED_WAITING(计时等待)
与 WAITING 类似,但有超时时间。以下方法会导致这个状态:
- Thread.sleep(long)
- Object.wait(long)
- Thread.join(long)
- LockSupport.parkNanos()
- LockSupport.parkUntil()
java
Thread sleepingThread = new Thread(() -> {
try {
Thread.sleep(5000); // 休眠5秒,进入TIMED_WAITING状态
} catch (InterruptedException e) {
e.printStackTrace();
}
});
sleepingThread.setName("sleeping-thread");
sleepingThread.start();
Thread.sleep(100); // 确保sleepingThread进入休眠状态
System.out.println("休眠中的线程状态:" + sleepingThread.getState()); // 输出: TIMED_WAITING
6. TERMINATED(终止)
线程执行完毕或因异常结束。
java
Thread terminatedThread = new Thread(() -> {
// 执行一些简短的任务
System.out.println("任务执行完毕");
});
terminatedThread.setName("terminated-thread");
terminatedThread.start();
Thread.sleep(100); // 确保线程有足够时间完成任务
System.out.println("任务完成后线程状态:" + terminatedThread.getState()); // 输出: TERMINATED
三种等待状态的对比
状态 | 触发条件 | 是否释放锁 | 恢复条件 | 典型使用场景 |
---|---|---|---|---|
BLOCKED | 等待进入 synchronized 同步块/方法 | 不持有锁 | 获得锁 | 多线程竞争共享资源 |
WAITING | Object.wait() Thread.join() LockSupport.park() | wait()释放锁 join/park 不涉及锁释放 | notify/notifyAll 目标线程结束 unpark | 线程协作,等待条件满足 |
TIMED_WAITING | Thread.sleep(time) Object.wait(time) Thread.join(time) | sleep 不释放锁 wait 释放锁 join 不涉及锁释放 | 时间到期或上述对应条件 | 超时等待,避免无限阻塞 |
二、start()与 run()的本质区别
初学者常犯的一个错误是直接调用线程的 run()方法,而不是 start()方法。这两者有本质区别:
调用 run()
java
Thread thread = new Thread(() -> {
System.out.println("当前线程: " + Thread.currentThread().getName());
});
thread.run(); // 直接调用run方法
输出:当前线程: main
调用 start()
java
Thread thread = new Thread(() -> {
System.out.println("当前线程: " + Thread.currentThread().getName());
});
thread.start(); // 调用start方法
输出:当前线程: Thread-0
区别分析
- run(): 普通方法调用,在当前线程(通常是 main 线程)执行线程体,没有创建新线程
- start(): 启动新线程,在新线程中执行 run()方法,实现了多线程并发执行
底层原理:start()方法会调用 native 方法 start0(),该方法会在 JVM 层面创建一个新的操作系统线程,并设置线程状态,最终导致 run()方法在新线程中执行。
常见错误案例
java
// 错误用法:重复调用start()
Thread thread = new Thread(() -> System.out.println("任务执行"));
thread.start();
thread.start(); // 抛出IllegalThreadStateException
// 错误理解:以为run()会启动线程
Thread thread2 = new Thread(() -> {
for(int i=0; i<1000; i++) {
System.out.println(i);
}
});
thread2.run(); // 在主线程中顺序执行,没有并发效果
三、线程控制方法详解
Java 提供了几个重要的线程控制方法,下面我们来详细解析:
1. sleep() - 线程休眠
使当前线程暂停执行指定的时间,进入 TIMED_WAITING 状态,但不会释放锁。
java
public static void sleepDemo() {
Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock) {
System.out.println("线程获取到锁");
try {
System.out.println("线程开始休眠5秒");
Thread.sleep(5000);
System.out.println("线程休眠结束");
} catch (InterruptedException e) {
System.out.println("线程被中断");
}
}
});
thread.setName("sleeping-thread");
thread.start();
// 给点时间让线程启动并获取锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 尝试获取锁,会被阻塞直到上面的线程释放锁
synchronized (lock) {
System.out.println("主线程获取到锁");
}
}
要点:
- sleep 是 Thread 类的静态方法,会暂停当前正在执行的线程
- 不会释放锁资源
- 可以被 interrupt()方法中断,抛出 InterruptedException
- sleep 结束后线程会自动回到 RUNNABLE 状态
- 长时间的 sleep()会占用线程资源而不执行工作,在线程池环境中可能导致性能下降
- 建议配合合理的超时机制使用,避免无限期阻塞
2. yield() - 线程让步
提示调度器当前线程愿意放弃 CPU 使用权,但调度器可以忽略这个提示。
java
public static void yieldDemo() {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("线程1: " + i);
if (i % 10 == 0) {
System.out.println("线程1让步");
Thread.yield();
}
}
});
thread1.setName("yield-thread");
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("线程2: " + i);
}
});
thread2.setName("normal-thread");
thread1.start();
thread2.start();
}
要点:
- yield 是 Thread 类的静态方法
- 只是提示调度器,没有强制性
- 从运行状态到就绪状态的转变(仍然是 RUNNABLE)
- 实际效果取决于操作系统的实现,不可靠
yield()在现代系统中的实际效果
尽管 yield()方法的理论目的是让出 CPU 时间片,但在现代操作系统和 JVM 实现中,它的实际效果往往不可预测:
- 大多数现代 CPU 调度器已经非常智能,能够有效分配时间片
- 不同 JVM 实现和操作系统对 yield()的处理方式不同
- 在某些系统上,yield()可能完全没有效果
- 在其他系统上,yield()可能导致当前线程被过度惩罚,长时间无法获得 CPU
在实际开发中:
- 避免使用 yield()来解决线程协作问题
- 如需控制线程执行顺序,应使用显式同步机制(如 CountDownLatch、CyclicBarrier 等)
- 如需控制执行时间分配,考虑使用线程优先级或更高级的调度框架
3. join() - 线程等待
让当前线程等待另一个线程执行完毕后再继续执行。
java
public static void joinDemo() {
Thread worker = new Thread(() -> {
System.out.println("工作线程开始执行...");
try {
// 模拟耗时操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("工作线程执行完毕");
});
worker.setName("worker-thread");
System.out.println("主线程启动工作线程");
worker.start();
System.out.println("主线程等待工作线程完成");
try {
worker.join(); // 主线程在这里等待worker线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程继续执行");
}
join()方法的重载版本:
join()
: 等待线程终止join(long millis)
: 等待指定的毫秒数join(long millis, int nanos)
: 等待指定的毫秒数加纳秒数
join()方法的内部实现原理:
java
// join()方法的内部实现原理(简化版)
public final synchronized void join(long millis) throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
// 当前线程在目标线程对象上等待
wait(millis);
}
} else if (millis == 0) {
while (isAlive()) {
// 无限期等待
wait(0);
}
}
// 注意:当目标线程终止时,JVM会调用notifyAll()唤醒所有等待线程
}
深入理解:当线程 A 调用线程 B 的 join()方法时,线程 A 会在线程 B 对象的监视器上等待。当线程 B 执行完毕(无论正常结束还是异常结束),JVM 会调用线程 B 对象的 notifyAll()方法,从而唤醒在其上等待的线程 A。这种设计确保了线程 A 能够在线程 B 结束后继续执行。
性能考虑:
- 无限期的 join()可能导致调用线程长时间等待,降低系统吞吐量
- 在线程池环境中,线程长时间阻塞会降低线程池效率
- 推荐使用 join(timeout)设置合理的等待超时时间
4. interrupt() - 线程中断
这是一种协作式的线程中断机制,用于通知线程应该停止或中断当前工作。
java
public static void interruptDemo() {
Thread sleepingThread = new Thread(() -> {
try {
System.out.println("线程开始休眠10秒");
Thread.sleep(10000);
System.out.println("休眠完成"); // 如果被中断,这行不会执行
} catch (InterruptedException e) {
System.out.println("线程被中断: " + e.getMessage());
} finally {
System.out.println("线程结束");
}
});
sleepingThread.setName("interrupted-thread");
sleepingThread.start();
// 主线程休眠2秒后中断sleepingThread
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程发出中断请求");
sleepingThread.interrupt();
}
中断机制相关方法:
interrupt()
: 请求中断线程isInterrupted()
: 检查线程是否被中断(不清除中断状态)Thread.interrupted()
: 静态方法,检查当前线程是否被中断(清除中断状态)
非阻塞场景的中断处理
在非阻塞状态下,interrupt()方法只会设置线程的中断标志,不会导致线程抛出 InterruptedException。此时,线程需要主动检查中断状态并作出响应:
java
public static void nonBlockingInterruptDemo() {
Thread worker = new Thread(() -> {
// 给线程起一个有意义的名称
Thread.currentThread().setName("worker-thread");
// 执行计算密集型任务,定期检查中断状态
long sum = 0;
System.out.println(Thread.currentThread().getName() + " 开始计算");
while (!Thread.currentThread().isInterrupted()) {
// 执行非阻塞计算
for (int i = 0; i < 1_000_000; i++) {
sum += i;
}
System.out.println("计算结果: " + sum);
sum = 0;
}
System.out.println(Thread.currentThread().getName() + " 检测到中断信号,优雅退出");
});
worker.start();
try {
Thread.sleep(100); // 让worker线程有时间开始工作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程发送中断请求");
worker.interrupt();
}
自定义阻塞方法的中断处理
当使用显式锁(如 ReentrantLock)或自定义阻塞机制时,需要特别注意中断处理:
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public static void customBlockingInterruptDemo() {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread waitingThread = new Thread(() -> {
try {
// 可中断的锁获取
lock.lockInterruptibly();
try {
System.out.println("线程获取到锁,等待条件");
// 等待条件,可被中断
condition.await();
System.out.println("条件满足,继续执行");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println("线程在等待锁或条件时被中断: " + e.getMessage());
}
});
waitingThread.setName("custom-waiting-thread");
waitingThread.start();
try {
Thread.sleep(1000); // 确保等待线程已经进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程发送中断请求");
waitingThread.interrupt();
}
中断机制的核心概念:
- interrupt()方法是一种协作式 而非强制式的线程中断机制
- 线程可以选择如何响应中断请求,甚至可以完全忽略它
- 良好的设计应确保线程能够及时检查并响应中断请求
isInterrupted() vs Thread.interrupted():
java
public static void interruptFlags() {
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程工作中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// sleep()方法被中断会清除中断标志
// 需要重新设置中断标志位以便外层循环检测
Thread.currentThread().interrupt();
System.out.println("中断发生,退出循环");
break;
}
}
});
thread.start();
// 主线程休眠3秒后中断工作线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
正确处理 InterruptedException 的模式
java
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
// 执行可能被中断的阻塞操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 重新设置中断标志并退出循环
Thread.currentThread().interrupt();
break;
}
}
} finally {
// 执行清理工作
System.out.println("线程结束,执行清理");
}
}
四、守护线程(Daemon Thread)
守护线程是一种特殊的线程,它在后台为其他非守护线程提供服务。当所有非守护线程结束时,无论守护线程是否完成工作,JVM 都会退出。
守护线程的特点
- 当 JVM 中只剩下守护线程时,JVM 会退出
- 必须在线程启动前设置守护状态
- 典型应用:垃圾回收器、监控线程、心跳线程等
守护线程示例
java
public static void daemonDemo() {
Thread daemonThread = new Thread(() -> {
while (true) {
try {
System.out.println("守护线程工作中...");
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
});
daemonThread.setName("daemon-thread");
// 设置为守护线程(必须在start之前)
daemonThread.setDaemon(true);
daemonThread.start();
// 主线程工作3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束,程序将退出");
// 程序退出,守护线程也会终止
}
为何守护线程的 finally 块不保证执行?
当 JVM 准备退出时,如果只剩下守护线程,JVM 会强制终止这些线程而不等待它们完成当前工作。这意味着:
java
public static void daemonFinalizationDemo() {
Thread daemon = new Thread(() -> {
try {
System.out.println("守护线程开始执行");
Thread.sleep(5000); // 模拟长时间操作
System.out.println("守护线程完成工作"); // 可能不会执行
} catch (InterruptedException e) {
System.out.println("守护线程被中断");
} finally {
System.out.println("守护线程的finally块"); // 可能不会执行
// 关键资源清理如文件关闭、连接释放可能不会发生
}
});
daemon.setDaemon(true);
daemon.start();
// 主线程很快结束
System.out.println("主线程结束,JVM准备退出");
}
守护线程应用场景
- 日志收集线程:周期性地将日志刷新到磁盘
- 缓存清理线程:后台清理过期缓存
- 心跳检测线程:定期发送心跳包
- 服务监控线程:监控服务健康状态
守护线程注意事项
- 守护线程创建的线程也是守护线程
- 守护线程不应该用于执行 I/O 操作,因为它们可能在操作完成前被强制终止
- finally 块在守护线程中不保证一定会执行
- 不要将连接池或事务性操作放在守护线程中
实践建议:不要在守护线程中执行以下操作:
- 文件或数据库操作(可能导致数据损坏)
- 网络连接管理(可能导致连接泄漏)
- 事务处理(可能导致事务不完整)
五、线程常见问题及解决方案
1. 线程执行不成功
问题描述:创建了线程对象,但没有执行任务
常见原因:
- 调用 run()而不是 start()
- 线程对象被垃圾回收
- 主线程结束太快
解决方案:
java
// 正确启动线程
Thread thread = new Thread(() -> {
System.out.println("任务执行");
});
thread.start(); // 不是thread.run()
// 保持对线程的引用
// 如果需要,可以使用join等待线程完成
2. 线程无法停止
问题描述:尝试停止一个正在运行的线程
常见错误:使用已废弃的 stop()、suspend()方法
为什么不应使用 stop()方法?
Thread.stop()方法在 Java 1.2 版本就被标记为废弃,主要原因包括:
- 不安全的资源释放:stop()会立即终止线程,不给线程任何机会执行清理工作,可能导致:
- 文件句柄未关闭
- 数据库连接未释放
- 网络套接字未关闭
- 临时文件未删除
- 数据不一致:强制终止可能使对象处于不一致状态
java
// 假设有转账操作
synchronized void transfer(Account from, Account to, int amount) {
from.debit(amount); // 假如在这行之后被stop()
to.credit(amount); // 这行永远不会执行
// 结果:钱从from账户扣除了,但没有加到to账户
}
- 锁状态不确定:线程被 stop()时会释放所有锁,但可能导致受保护数据的不一致状态暴露给其他线程
推荐解决方案:使用中断机制或状态标志位
java
// 通过标志位控制线程结束
class StoppableTask implements Runnable {
private volatile boolean stopRequested = false;
public void requestStop() {
stopRequested = true;
}
@Override
public void run() {
while (!stopRequested) {
// 执行任务
System.out.println("线程工作中...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// 响应中断
Thread.currentThread().interrupt(); // 重设中断标志
break;
}
}
System.out.println("线程正常退出");
}
}
// 使用示例
public static void stoppableThreadDemo() {
StoppableTask task = new StoppableTask();
Thread thread = new Thread(task);
thread.start();
// 主线程工作3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 请求线程停止
task.requestStop();
}
3. 死锁问题
问题描述:两个或多个线程互相等待对方持有的锁,形成环路等待
死锁示例:
java
public static void deadlockDemo() {
Object resource1 = new Object();
Object resource2 = new Object();
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("线程1获取资源1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1等待资源2");
synchronized (resource2) {
System.out.println("线程1获取资源2");
}
}
});
thread1.setName("deadlock-thread-1");
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("线程2获取资源2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2等待资源1");
synchronized (resource1) {
System.out.println("线程2获取资源1");
}
}
});
thread2.setName("deadlock-thread-2");
thread1.start();
thread2.start();
}
其他常见并发问题
除了死锁外,多线程编程中还存在以下并发问题:
线程饥饿(Starvation)
当线程长时间无法获取所需资源而无法执行时,就会发生线程饥饿:
java
public static void starvationDemo() {
Object lock = new Object();
// 创建一个高优先级线程,它会长时间占用锁
Thread highPriorityThread = new Thread(() -> {
synchronized (lock) {
System.out.println("高优先级线程获取到锁");
while (true) {
// 持续占用锁不释放
}
}
});
highPriorityThread.setPriority(Thread.MAX_PRIORITY);
highPriorityThread.setName("high-priority");
// 创建低优先级线程,它很难获取到锁
Thread lowPriorityThread = new Thread(() -> {
synchronized (lock) {
//
System.out.println("低优先级线程获取到锁");
}
});
lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
lowPriorityThread.setName("low-priority");
highPriorityThread.start();
lowPriorityThread.start();
}
避免饥饿的策略:
- 使用公平锁(如 ReentrantLock(true))
- 避免长时间持有锁
- 适当调整线程优先级
注意:在现代 JVM 中,线程优先级的效果可能不如预期。优先级调整是一种建议而非强制执行的机制,不同操作系统实现差异很大。推荐优先通过合理的锁使用策略(减少锁持有时间、使用公平锁等)来避免线程饥饿,而不是过度依赖优先级调整。
活锁(Livelock)
当线程不断相互礼让,都无法向前推进时,就会发生活锁:
java
public static void simpleLivelockDemo() {
final AtomicBoolean firstThreadGiveWay = new AtomicBoolean(true);
final AtomicBoolean secondThreadGiveWay = new AtomicBoolean(true);
Thread thread1 = new Thread(() -> {
while (true) {
// 第一个线程检查"对方是否在让路"
if (secondThreadGiveWay.get()) {
// 如果对方在让路,自己也让路
System.out.println("线程1说:对方在让路,我也让路");
firstThreadGiveWay.set(true);
} else {
// 如果对方不让路,自己也不让了,开始工作
System.out.println("线程1说:对方不让路了,我开始工作");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return;
}
}
});
Thread thread2 = new Thread(() -> {
while (true) {
// 第二个线程检查"对方是否在让路"
if (firstThreadGiveWay.get()) {
// 如果对方在让路,自己也让路
System.out.println("线程2说:对方在让路,我也让路");
secondThreadGiveWay.set(true);
} else {
// 如果对方不让路,自己也不让了,开始工作
System.out.println("线程2说:对方不让路了,我开始工作");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return;
}
}
});
thread1.start();
thread2.start();
// 这个活锁将永远持续下去,因为两个线程都在互相礼让
// 在实际应用中,可以通过随机等待或优先级解决
}
避免活锁的策略:
- 引入随机因素(如随机等待时间)
- 使用优先级或资源排序
- 设置超时机制
4. 线程异常处理
线程中未捕获的异常不会被传递到主线程,需要特殊处理:
java
public static void exceptionHandlerDemo() {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
throw new RuntimeException("线程执行异常");
});
thread.setName("exception-thread");
// 设置未捕获异常处理器
thread.setUncaughtExceptionHandler((t, e) -> {
System.out.println("捕获到线程 " + t.getName() + " 的异常:" + e.getMessage());
// 可以记录日志、发送告警等
});
thread.start();
}
六、总结
类别 | 方法/状态 | 特点 | 注意事项 |
---|---|---|---|
线程状态 | NEW | 线程已创建未启动 | 使用 start()方法启动 |
RUNNABLE | 包含就绪和运行中 | 线程获得 CPU 时间片才真正运行 | |
BLOCKED | 等待获取 synchronized 锁 | 避免长时间阻塞 | |
WAITING | 无限期等待其他线程操作 | 可能导致程序挂起 | |
TIMED_WAITING | 有超时时间的等待 | 设置合理的超时时间 | |
TERMINATED | 线程执行完毕 | 可以用 isAlive()检查 | |
线程控制 | start() | 启动新线程 | 不能重复调用 |
run() | 普通方法调用,不创建新线程 | 不要直接调用 run()方法 | |
sleep() | 线程休眠,不释放锁 | 处理 InterruptedException | |
yield() | 线程让步,提示调度器 | 效果不可靠,依赖系统实现 | |
join() | 等待其他线程完成 | 可能导致当前线程阻塞 | |
interrupt() | 中断线程,协作式机制 | 结合 isInterrupted()使用 | |
线程类型 | 用户线程 | 默认线程类型 | JVM 等待用户线程结束 |
守护线程 | 为其他线程服务的后台线程 | 不执行重要任务,finally 块不保证执行 | |
并发问题 | 死锁 | 线程互相等待对方持有的锁 | 按固定顺序获取锁,使用 tryLock() |
活锁 | 线程不断相互礼让 | 引入随机因素,设置优先级 | |
饥饿 | 线程长期无法获取资源 | 使用公平锁,避免长时间持有锁 |
通过本文,我们深入了解了 Java 线程的生命周期和基础操作方法。掌握这些知识对于编写高质量的多线程程序至关重要。在实际开发中,请记住:理解线程状态转换、合理使用线程控制方法、妥善处理线程异常、选择适当的线程通信机制,这些都是构建稳定多线程应用的基石。
在下一篇文章中,我们将探讨线程安全问题与基本解决方案,敬请期待!
感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!
如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~