Java--多线程--04
以下是针对线程状态的极其详细讲解。本篇将按照线程的六种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)逐一展开,每个状态包括定义、原因、代码案例、练习,以及易错点、混淆点和容易搞错的点。
线程状态概述
线程是程序执行的最小单位,其状态反映了线程在生命周期中的行为。理解这些状态对调试并发问题、避免死锁和提高性能至关重要。Java中,线程状态通过Thread.State枚举表示,包括:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。每个状态都有特定触发条件和行为,下面我们逐一深入讲解。
1. NEW(新建)
定义与原因
NEW状态表示线程对象已被创建(例如,通过new Thread()),但尚未调用start()方法启动。此时,线程只是内存中的一个对象,没有分配系统资源(如CPU时间片),不会执行run()方法。只有调用start()后,线程才进入RUNNABLE状态。
代码案例
public class NewStateExample {
public static void main(String[] args) {
// 创建线程对象,但未调用start()
Thread thread = new Thread(() -> {
System.out.println("线程运行中");
});
// 打印线程状态:应为 NEW
System.out.println("线程状态: " + thread.getState()); // 输出: NEW
}
}
运行此代码,线程状态为NEW,因为start()未被调用。
练习
每个练习旨在加深理解,请先尝试解答,再参考解释。
-
练习1:基础状态检查
编写代码创建两个线程对象,但不调用start()。打印它们的线程状态。确认状态是否为NEW。// 示例代码框架 Thread t1 = new Thread(() -> {}); Thread t2 = new Thread(() -> {}); System.out.println("t1状态: " + t1.getState()); System.out.println("t2状态: " + t2.getState());解释:状态应为NEW。易错点:误以为线程创建后自动启动。
-
练习2:方法调用影响
在NEW状态下,线程能否调用run()方法?如果可以,调用后状态会变吗?编写代码测试。Thread thread = new Thread(() -> { System.out.println("run()方法执行"); }); thread.run(); // 直接调用run() System.out.println("状态: " + thread.getState());解释:直接调用
run()不会改变状态(仍为NEW),因为它只是普通方法调用,而非启动线程。易错点:混淆run()和start(),以为run()能启动线程。 -
练习3:状态转换模拟
创建一个线程对象,先打印状态(应为NEW),然后调用start(),再打印状态。观察变化。Thread thread = new Thread(() -> {}); System.out.println("start前状态: " + thread.getState()); // NEW thread.start(); System.out.println("start后状态: " + thread.getState()); // 可能为RUNNABLE解释:调用
start()后状态变为RUNNABLE。易错点:在多线程环境中,状态打印可能受调度影响,不一定是即时变化。 -
练习4:错误场景分析
假设代码中多次调用start()方法(例如:thread.start(); thread.start();)。会发生什么?状态如何变化?Thread thread = new Thread(() -> {}); thread.start(); thread.start(); // 第二次调用start()解释:抛出
IllegalThreadStateException,因为线程不能重复启动。状态在第一次start()后变为RUNNABLE或TERMINATED,但不会回到NEW。易错点:忽略线程状态的生命周期,以为可以重复启动。 -
练习5:资源占用问题
在NEW状态下,线程是否占用系统资源(如CPU或内存)?编写代码创建1000个NEW线程,观察内存使用(可用Runtime.getRuntime().totalMemory())。public class ResourceTest { public static void main(String[] args) { for (int i = 0; i < 1000; i++) { new Thread(() -> {}); // 只创建,不start } System.out.println("内存占用: " + Runtime.getRuntime().totalMemory()); } }解释:NEW线程占用内存(对象开销),但不占用CPU。易错点:误以为NEW线程无开销,导致内存泄漏。
易错点与混淆点
- 易错点:NEW状态容易被忽略,因为线程不执行任何任务。开发者可能在创建线程后忘记调用
start(),导致程序逻辑错误。 - 混淆点:NEW状态与“未初始化”混淆。NEW线程对象已初始化,但未激活。与RUNNABLE混淆,因为两者都涉及线程对象存在。
- 容易搞错的点:直接调用
run()方法不会改变状态,它只是同步执行代码块,而非启动新线程。线程启动必须通过start()。
2. RUNNABLE(可运行)
定义与原因
RUNNABLE状态表示线程已启动(调用start()),正在运行或等待CPU时间片。在Java中,RUNNABLE统一表示就绪(Ready)和运行(Running)状态:就绪时线程等待调度,运行时占用CPU执行。操作系统层面,就绪和运行是分开的,但Java API只暴露RUNNABLE。线程可通过yield()让出CPU,但状态不变。
代码案例
public class RunnableStateExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
// 模拟运行
}
});
thread.start();
System.out.println("线程状态: " + thread.getState()); // 输出: RUNNABLE
}
}
线程启动后进入RUNNABLE状态,可能在运行或就绪。
练习
-
练习1:状态打印时机
启动一个线程,在循环中打印状态。观察状态是否总为RUNNABLE。Thread thread = new Thread(() -> { while (true) { /* 空循环 */ } }); thread.start(); for (int i = 0; i < 5; i++) { System.out.println("状态: " + thread.getState()); // 应为RUNNABLE Thread.sleep(100); // 主线程睡眠,让子线程运行 }解释:状态始终为RUNNABLE。易错点:误以为在循环中状态会变化,但RUNNABLE涵盖就绪和运行。
-
练习2:CPU竞争模拟
创建两个高优先级线程,竞争CPU。打印状态,观察调度。Thread t1 = new Thread(() -> { while(true) {} }); Thread t2 = new Thread(() -> { while(true) {} }); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MAX_PRIORITY); t1.start(); t2.start(); System.out.println("t1状态: " + t1.getState()); // RUNNABLE System.out.println("t2状态: " + t2.getState()); // RUNNABLE解释:两者均为RUNNABLE,但操作系统可能切换运行状态。易错点:以为优先级高的线程总在运行状态。
-
练习3:yield()方法影响
在线程中调用Thread.yield(),打印状态。状态会变吗?Thread thread = new Thread(() -> { while (true) { Thread.yield(); // 让出CPU } }); thread.start(); System.out.println("状态: " + thread.getState()); // RUNNABLE解释:状态仍为RUNNABLE,
yield()只是提示调度器让出CPU,不改变状态。易错点:误以为yield()使线程进入WAITING。 -
练习4:多线程并发
启动10个线程,每个打印自己的状态。所有状态应为RUNNABLE吗?for (int i = 0; i < 10; i++) { new Thread(() -> { System.out.println("状态: " + Thread.currentThread().getState()); }).start(); }解释:输出可能显示RUNNABLE,但受调度影响,打印时线程可能在运行或就绪。易错点:在多核CPU上,多个线程可同时运行,状态打印可能不一致。
-
练习5:状态与性能
编写代码,测量线程在RUNNABLE状态下的CPU占用(可用ThreadMXBean)。观察就绪和运行的区别。import java.lang.management.ThreadMXBean; import java.lang.management.ManagementFactory; public class CpuUsage { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { while(true) {} }); thread.start(); ThreadMXBean bean = ManagementFactory.getThreadMXBean(); long cpuTime = bean.getThreadCpuTime(thread.getId()); System.out.println("CPU时间: " + cpuTime); // 非零表示运行过 System.out.println("状态: " + thread.getState()); // RUNNABLE } }解释:RUNNABLE状态不区分就绪和运行,但CPU时间非零表示线程曾运行。易错点:混淆状态和实际CPU占用。
易错点与混淆点
- 易错点:RUNNABLE状态在Java中统一表示,但开发者可能误以为线程总在运行,忽略就绪状态。导致性能优化时忽略调度开销。
- 混淆点:与BLOCKED混淆,因为两者都可能“等待”,但RUNNABLE是主动等待CPU,BLOCKED是被动等待锁。
- 容易搞错的点:线程在I/O操作(如文件读写)时状态仍为RUNNABLE,因为I/O在Java中可能不阻塞线程(使用NIO时)。操作系统层可能阻塞,但Java状态不变。
3. BLOCKED(阻塞)
定义与原因
BLOCKED状态表示线程因等待监视器锁(如synchronized关键字)而阻塞。只有当线程试图进入synchronized代码块或方法,但锁被其他线程持有时,才进入此状态。一旦锁释放,线程自动转为RUNNABLE。注意:BLOCKED仅针对synchronized锁,不适用于其他锁机制(如ReentrantLock)。
代码案例
public class BlockedStateExample {
public static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
while (true) {} // 持有锁不释放
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) { // 等待锁
System.out.println("t2获得锁");
}
});
t1.start();
t2.start();
System.out.println("t2状态: " + t2.getState()); // 可能为BLOCKED
}
}
t2在等待锁时进入BLOCKED状态。
练习
-
练习1:锁竞争
创建两个线程竞争同一锁。打印等待线程的状态。Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { try { Thread.sleep(1000); } catch (InterruptedException e) {} } }); Thread t2 = new Thread(() -> { synchronized (lock) { // 等待锁 } }); t1.start(); t2.start(); Thread.sleep(100); // 让t1先运行 System.out.println("t2状态: " + t2.getState()); // BLOCKED解释:t2在等待锁时状态为BLOCKED。易错点:未确保t1先获得锁,导致状态打印错误。
-
练习2:错误锁释放
线程在BLOCKED状态下,如何被唤醒?修改代码,让t1释放锁后观察t2状态变化。// 同上代码,但t1 sleep后释放锁 // t2状态在t1释放后变为RUNNABLE解释:锁释放后,t2自动转为RUNNABLE。易错点:误以为需要显式唤醒(如
notify()),但BLOCKED是自动恢复。 -
练习3:BLOCKED vs WAITING
使用wait()方法,线程状态是什么?编写代码对比。Object lock = new Object(); Thread t1 = new Thread(() -> { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) {} // 调用wait() } }); t1.start(); Thread.sleep(100); System.out.println("t1状态: " + t1.getState()); // WAITING, not BLOCKED解释:调用
wait()进入WAITING状态,而非BLOCKED。易错点:混淆BLOCKED(锁竞争)和WAITING(主动等待)。 -
练习4:死锁检测
创建死锁场景:两个线程互相等待锁。打印状态,识别BLOCKED线程。Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) {} // 等待lock2 } }); Thread t2 = new Thread(() -> { synchronized (lock2) { synchronized (lock1) {} // 等待lock1 } }); t1.start(); t2.start(); Thread.sleep(1000); System.out.println("t1状态: " + t1.getState()); // BLOCKED System.out.println("t2状态: " + t2.getState()); // BLOCKED解释:两者均BLOCKED,形成死锁。易错点:死锁时线程永久BLOCKED,需外部干预。
-
练习5:非synchronized锁
使用ReentrantLock代替synchronized,线程在等待锁时状态是什么?import java.util.concurrent.locks.ReentrantLock; ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { lock.lock(); // 持有锁 while (true) {} }); Thread t2 = new Thread(() -> { lock.lock(); // 等待锁 }); t1.start(); t2.start(); System.out.println("t2状态: " + t2.getState()); // WAITING, 非BLOCKED解释:
ReentrantLock的等待使线程进入WAITING状态(通过LockSupport.park()),而非BLOCKED。易错点:以为所有锁机制都导致BLOCKED状态。
易错点与混淆点
- 易错点:BLOCKED状态只发生在
synchronized锁竞争时。开发者可能在其他等待场景(如I/O)误判为BLOCKED。 - 混淆点:与WAITING状态混淆,因为两者都涉及“等待”,但BLOCKED是自动恢复的锁等待,WAITING需要显式唤醒。
- 容易搞错的点:在
ReentrantLock或Condition上等待时,状态是WAITING或TIMED_WAITING,不是BLOCKED。忽略这点会导致调试错误。
4. WAITING(等待)
定义与原因
WAITING状态表示线程因调用无期限等待方法而暂停,例如Object.wait()、Thread.join()或LockSupport.park()。线程进入此状态后,不会消耗CPU资源,必须由其他线程显式唤醒(如notify()或unpark())。WAITING常用于线程间协调,如生产者-消费者模型。
代码案例
public class WaitingStateExample {
public static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 进入WAITING
} catch (InterruptedException e) {}
}
});
thread.start();
Thread.sleep(100); // 确保thread进入wait
System.out.println("线程状态: " + thread.getState()); // WAITING
}
}
线程调用wait()后进入WAITING状态。
练习
-
练习1:wait()方法使用
线程调用wait()后,状态如何?编写代码唤醒它,观察状态变化。Object lock = new Object(); Thread t = new Thread(() -> { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) {} System.out.println("唤醒后"); } }); t.start(); Thread.sleep(100); System.out.println("状态: " + t.getState()); // WAITING synchronized (lock) { lock.notify(); // 唤醒 } Thread.sleep(100); System.out.println("状态: " + t.getState()); // RUNNABLE或TERMINATED解释:唤醒后状态变为RUNNABLE。易错点:忘记在同步块内调用
notify(),导致唤醒失败。 -
练习2:join()方法
主线程调用子线程的join(),状态是什么?Thread child = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) {} }); child.start(); child.join(); // 主线程调用join() // 在主线程中打印自身状态? System.out.println("主线程状态: " + Thread.currentThread().getState()); // RUNNABLE解释:
join()使调用线程(主线程)进入WAITING状态,等待子线程结束。但打印自身状态时为RUNNABLE,因为join()内部实现使用WAITING。易错点:误以为join()改变的是子线程状态。 -
练习3:永久等待风险
编写代码,线程wait()但无唤醒逻辑。状态会变吗?如何避免死锁?Object lock = new Object(); Thread t = new Thread(() -> { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) {} } }); t.start(); // 无唤醒代码 Thread.sleep(2000); System.out.println("状态: " + t.getState()); // 仍为WAITING解释:状态永久WAITING,导致线程泄漏。易错点:忘记设计唤醒机制。
-
练习4:多线程协调
实现生产者-消费者模型,生产者wait()当缓冲区满。打印状态确认WAITING。import java.util.Queue; import java.util.LinkedList; Queue<Integer> buffer = new LinkedList<>(); int maxSize = 1; Object lock = new Object(); Thread producer = new Thread(() -> { synchronized (lock) { while (buffer.size() == maxSize) { try { lock.wait(); } catch (InterruptedException e) {} // WAITING } buffer.add(1); lock.notify(); } }); producer.start(); // 类似创建consumer线程解释:当缓冲区满时,生产者进入WAITING。易错点:未在循环中检查条件(虚假唤醒问题)。
-
练习5:LockSupport.park()
使用LockSupport.park()使线程进入WAITING。如何唤醒?Thread thread = new Thread(() -> { LockSupport.park(); // 进入WAITING }); thread.start(); Thread.sleep(100); System.out.println("状态: " + thread.getState()); // WAITING LockSupport.unpark(thread); // 唤醒解释:
park()进入WAITING,unpark()唤醒。易错点:unpark()可在park()前调用,防止永久等待。
易错点与混淆点
- 易错点:WAITING状态需要显式唤醒,开发者可能忘记调用
notify()或unpark(),导致线程永久挂起。 - 混淆点:与BLOCKED混淆,因为两者都“等待”,但WAITING是主动调用等待方法,BLOCKED是锁竞争被动等待。
- 容易搞错的点:
Thread.join()使调用线程进入WAITING,而非被join的线程。忽略这点会导致状态判断错误。
5. TIMED_WAITING(计时等待)
定义与原因
TIMED_WAITING状态表示线程因调用有时间限制的等待方法而暂停,例如Thread.sleep(long millis)、Object.wait(long timeout)或Thread.join(long millis)。线程在指定时间后自动唤醒(或提前被中断),无需显式唤醒。常用于定时任务或超时控制。
代码案例
public class TimedWaitingStateExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000); // 进入TIMED_WAITING
} catch (InterruptedException e) {}
});
thread.start();
Thread.sleep(100); // 确保thread进入sleep
System.out.println("线程状态: " + thread.getState()); // TIMED_WAITING
}
}
线程sleep()时进入TIMED_WAITING状态。
练习
-
练习1:sleep()方法
线程调用sleep(1000),打印状态。时间结束后状态变化?Thread t = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) {} }); t.start(); Thread.sleep(100); System.out.println("状态: " + t.getState()); // TIMED_WAITING Thread.sleep(2000); System.out.println("状态: " + t.getState()); // TERMINATED解释:睡眠结束后状态变为TERMINATED(如果run()结束)。易错点:单位错误(sleep参数是毫秒)。
-
练习2:wait(timeout)
使用wait(500),状态如何?与sleep()对比。Object lock = new Object(); Thread t = new Thread(() -> { synchronized (lock) { try { lock.wait(500); } catch (InterruptedException e) {} // TIMED_WAITING } }); t.start(); Thread.sleep(100); System.out.println("状态: " + t.getState()); // TIMED_WAITING解释:状态为TIMED_WAITING。易错点:
wait(timeout)必须在同步块内调用,否则抛IllegalMonitorStateException。 -
练习3:join(timeout)
主线程调用子线程的join(1000),状态变化?Thread child = new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) {} }); child.start(); child.join(1000); // 主线程TIMED_WAITING System.out.println("主线程状态: " + Thread.currentThread().getState()); // RUNNABLE解释:
join(timeout)使调用线程进入TIMED_WAITING,但打印自身状态时为RUNNABLE。易错点:混淆超时和实际线程结束。 -
练习4:中断处理
线程在TIMED_WAITING时被中断(interrupt()),状态如何?编写代码测试。Thread t = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("中断捕获"); } }); t.start(); Thread.sleep(100); t.interrupt(); // 中断 Thread.sleep(100); System.out.println("状态: " + t.getState()); // RUNNABLE或TERMINATED解释:中断后线程唤醒,状态变为RUNNABLE。易错点:未处理
InterruptedException,导致行为未定义。 -
练习5:定时任务模拟
创建线程每秒执行任务,使用sleep(1000)。打印状态确认TIMED_WAITING。Thread timer = new Thread(() -> { while (true) { System.out.println("任务执行"); try { Thread.sleep(1000); } catch (InterruptedException e) {} } }); timer.start(); Thread.sleep(1500); System.out.println("状态: " + timer.getState()); // TIMED_WAITING (在sleep期间)解释:在
sleep()期间状态为TIMED_WAITING。易错点:循环中使用sleep()可能导致精度问题。
易错点与混淆点
- 易错点:时间参数单位错误(如秒vs毫秒),导致等待时间不符预期。
sleep()不会释放锁,而wait(timeout)会。 - 混淆点:与WAITING混淆,因为两者都“等待”,但TIMED_WAITING有超时机制,自动唤醒。
- 容易搞错的点:
Thread.join(timeout)如果超时,线程未结束,调用线程仍继续执行,但被join的线程可能还在运行。
6. TERMINATED(终止)
定义与原因
TERMINATED状态表示线程已执行完毕(run()方法正常结束)或因未捕获异常退出。线程进入此状态后,不能被重启(调用start()会抛异常),系统资源被释放。线程对象仍存在,但状态不可变。
代码案例
public class TerminatedStateExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
// run()方法执行完毕
});
thread.start();
thread.join(); // 等待结束
System.out.println("线程状态: " + thread.getState()); // TERMINATED
}
}
线程结束后状态为TERMINATED。
练习
-
练习1:正常结束
线程执行简单任务后结束。打印状态。Thread t = new Thread(() -> {}); t.start(); t.join(); System.out.println("状态: " + t.getState()); // TERMINATED解释:状态为TERMINATED。易错点:未使用
join()确保线程结束,导致状态打印过早。 -
练习2:异常终止
线程抛出未捕获异常,状态如何?Thread t = new Thread(() -> { throw new RuntimeException("错误"); }); t.start(); t.join(); System.out.println("状态: " + t.getState()); // TERMINATED解释:未捕获异常导致TERMINATED。易错点:忽略异常处理,线程静默终止。
-
练习3:重启尝试
线程终止后,调用start()方法。会发生什么?Thread t = new Thread(() -> {}); t.start(); t.join(); t.start(); // 第二次start解释:抛出
IllegalThreadStateException。易错点:误以为线程可重用。 -
练习4:isAlive()方法
使用isAlive()检查线程是否存活。终止后返回什么?Thread t = new Thread(() -> {}); t.start(); t.join(); System.out.println("isAlive: " + t.isAlive()); // false System.out.println("状态: " + t.getState()); // TERMINATED解释:
isAlive()返回false,状态为TERMINATED。易错点:混淆isAlive()和状态API。 -
练习5:线程池影响
在线程池中,线程执行任务后状态如何?(模拟使用ExecutorService)import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; ExecutorService pool = Executors.newSingleThreadExecutor(); pool.execute(() -> {}); // 线程池线程可能被重用,状态不一定是TERMINATED pool.shutdown();解释:线程池线程任务结束后可能回到RUNNABLE(等待新任务),不直接TERMINATED。易错点:以为所有线程结束时都TERMINATED,忽略线程池机制。
易错点与混淆点
- 易错点:线程终止后仍尝试操作(如
start()),导致异常。开发者可能未处理线程异常,导致意外终止。 - 混淆点:与RUNNABLE混淆,因为线程结束前可能短暂处于RUNNABLE。
isAlive()方法更直接判断存活。 - 容易搞错的点:在线程池中,线程对象被重用,状态不固定;直接创建线程时,TERMINATED是最终状态。
总结与常见混淆点对比
线程状态反映了并发编程的核心行为。以下是关键混淆点总结:
- RUNNABLE vs BLOCKED:RUNNABLE是主动等待CPU,BLOCKED是被动等待锁。I/O阻塞在Java中可能不改变状态。
- BLOCKED vs WAITING:BLOCKED只针对
synchronized锁,自动恢复;WAITING需要显式唤醒,适用于wait()或park()。 - WAITING vs TIMED_WAITING:WAITING无超时,TIMED_WAITING有超时自动唤醒。
sleep()是TIMED_WAITING,但不会释放锁。 - TERMINATED:线程结束后不可重启,
isAlive()为false。
本篇的讲解就在这里啦~希望堆大家有所帮助!~
更多推荐

所有评论(0)