学习目标

  • 了解Java线程的六大状态及其转换过程
  • 掌握线程状态的判断和监控方法
  • 理解如何合理地控制线程状态转换
  • 学会识别和解决线程卡死的常见问题

1. 线程的六大状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED

在Java中,线程的生命周期被划分为六种状态,这些状态定义在Thread.State枚举类中。理解这些状态及其转换对于开发高效的多线程应用至关重要。

1.1 NEW(新建)

当线程对象被创建但还未调用start()方法时,线程处于NEW状态。

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 演示线程的NEW状态
  4. */
  5. public class ThreadNewStateDemo {
  6. public static void main(String[] args) {
  7. // 创建线程对象,此时线程处于NEW状态
  8. Thread thread = new Thread(() -> {
  9. System.out.println("线程执行中...");
  10. });
  11. // 检查线程状态
  12. System.out.println("线程创建后的状态: " + thread.getState());
  13. // 验证是否为NEW状态
  14. System.out.println("是否为NEW状态: " + (thread.getState() == Thread.State.NEW));
  15. }
  16. }

1.2 RUNNABLE(可运行)

当线程调用了start()方法后,它的状态变为RUNNABLE。这表示线程已经被启动,正在等待被线程调度器分配CPU时间片。

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 演示线程的RUNNABLE状态
  4. */
  5. public class ThreadRunnableStateDemo {
  6. public static void main(String[] args) {
  7. Thread thread = new Thread(() -> {
  8. // 一个长时间运行的任务,确保线程保持RUNNABLE状态
  9. for (long i = 0; i < 1_000_000_000L; i++) {
  10. // 执行一些计算,让线程保持活动状态
  11. Math.sqrt(i);
  12. }
  13. });
  14. // 启动线程,状态从NEW变为RUNNABLE
  15. thread.start();
  16. // 给线程一点时间启动
  17. try {
  18. Thread.sleep(10);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. // 检查线程状态
  23. System.out.println("线程启动后的状态: " + thread.getState());
  24. // 等待线程结束
  25. try {
  26. thread.join();
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }

1.3 BLOCKED(阻塞)

当线程尝试获取一个被其他线程持有的对象锁时,该线程会进入BLOCKED状态。线程会一直保持BLOCKED状态,直到它获得了锁。

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 演示线程的BLOCKED状态
  4. */
  5. public class ThreadBlockedStateDemo {
  6. // 共享资源,用于线程同步
  7. private static final Object lock = new Object();
  8. public static void main(String[] args) {
  9. // 创建第一个线程,它会获取并持有锁
  10. Thread thread1 = new Thread(() -> {
  11. System.out.println("线程1开始执行");
  12. synchronized (lock) {
  13. // 持有锁10秒钟
  14. System.out.println("线程1获取到锁");
  15. try {
  16. Thread.sleep(10000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println("线程1释放锁");
  21. }
  22. });
  23. // 创建第二个线程,它会尝试获取锁但会被阻塞
  24. Thread thread2 = new Thread(() -> {
  25. System.out.println("线程2开始执行");
  26. synchronized (lock) {
  27. // 如果获取到锁,则执行这里的代码
  28. System.out.println("线程2获取到锁");
  29. }
  30. });
  31. // 启动第一个线程
  32. thread1.start();
  33. // 给线程1一点时间获取锁
  34. try {
  35. Thread.sleep(100);
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. // 启动第二个线程
  40. thread2.start();
  41. // 给线程2一点时间尝试获取锁
  42. try {
  43. Thread.sleep(100);
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. }
  47. // 检查线程2的状态
  48. System.out.println("线程2的状态: " + thread2.getState());
  49. // 等待两个线程都结束
  50. try {
  51. thread1.join();
  52. thread2.join();
  53. } catch (InterruptedException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. }

1.4 WAITING(等待)

当线程调用某些方法主动放弃CPU执行权,进入无限期等待状态时,线程处于WAITING状态。以下方法可以导致线程进入WAITING状态:

  • Object.wait():等待其他线程调用同一对象的notify()notifyAll()
  • Thread.join():等待指定的线程结束
  • LockSupport.park():等待许可
  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 演示线程的WAITING状态
  4. */
  5. public class ThreadWaitingStateDemo {
  6. private static final Object lock = new Object();
  7. public static void main(String[] args) {
  8. // 创建等待线程
  9. Thread waitingThread = new Thread(() -> {
  10. synchronized (lock) {
  11. try {
  12. System.out.println("等待线程进入等待状态...");
  13. lock.wait(); // 调用wait()方法,线程进入WAITING状态
  14. System.out.println("等待线程被唤醒,继续执行");
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. });
  20. // 创建唤醒线程
  21. Thread notifyThread = new Thread(() -> {
  22. try {
  23. // 先休眠2秒,确保waitingThread已经进入WAITING状态
  24. Thread.sleep(2000);
  25. synchronized (lock) {
  26. System.out.println("唤醒线程准备唤醒等待线程...");
  27. lock.notify(); // 唤醒在lock上等待的线程
  28. System.out.println("唤醒线程已发出唤醒信号");
  29. }
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. });
  34. // 启动等待线程
  35. waitingThread.start();
  36. // 给等待线程一点时间进入等待状态
  37. try {
  38. Thread.sleep(500);
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. // 检查等待线程的状态
  43. System.out.println("等待线程的状态: " + waitingThread.getState());
  44. // 启动唤醒线程
  45. notifyThread.start();
  46. // 等待两个线程结束
  47. try {
  48. waitingThread.join();
  49. notifyThread.join();
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. }

1.5 TIMED_WAITING(限时等待)

当线程调用带有超时参数的方法主动放弃CPU执行权,进入有限期等待状态时,线程处于TIMED_WAITING状态。以下方法可以导致线程进入TIMED_WAITING状态:

  • Thread.sleep(long millis):休眠指定的毫秒数
  • Object.wait(long timeout):等待指定的毫秒数,或者直到被通知
  • Thread.join(long millis):等待指定线程结束,但最多等待指定的毫秒数
  • LockSupport.parkNanos(long nanos):等待许可,但最多等待指定的纳秒数
  • LockSupport.parkUntil(long deadline):等待许可,但最多等待到指定的时间戳
  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 演示线程的TIMED_WAITING状态
  4. */
  5. public class ThreadTimedWaitingStateDemo {
  6. public static void main(String[] args) {
  7. Thread sleepingThread = new Thread(() -> {
  8. try {
  9. System.out.println("线程开始休眠5秒...");
  10. Thread.sleep(5000); // 线程休眠5秒,进入TIMED_WAITING状态
  11. System.out.println("线程休眠结束");
  12. } catch (InterruptedException e) {
  13. System.out.println("线程被中断");
  14. }
  15. });
  16. // 启动线程
  17. sleepingThread.start();
  18. // 给线程一点时间进入休眠状态
  19. try {
  20. Thread.sleep(100);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. // 检查线程状态
  25. System.out.println("休眠线程的状态: " + sleepingThread.getState());
  26. // 等待线程结束
  27. try {
  28. sleepingThread.join();
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }

1.6 TERMINATED(终止)

当线程正常结束或因异常而结束时,线程进入TERMINATED状态。

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 演示线程的TERMINATED状态
  4. */
  5. public class ThreadTerminatedStateDemo {
  6. public static void main(String[] args) {
  7. Thread thread = new Thread(() -> {
  8. System.out.println("线程开始执行...");
  9. // 执行一些简单的任务后结束
  10. for (int i = 0; i < 5; i++) {
  11. System.out.println("线程执行中: " + i);
  12. try {
  13. Thread.sleep(200);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. System.out.println("线程执行结束");
  19. });
  20. // 启动线程
  21. thread.start();
  22. // 等待线程执行完毕
  23. try {
  24. thread.join();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. // 检查线程状态
  29. System.out.println("线程的状态: " + thread.getState());
  30. // 验证是否为TERMINATED状态
  31. System.out.println("是否为TERMINATED状态: " + (thread.getState() == Thread.State.TERMINATED));
  32. }
  33. }

2. 状态转换图解与示例代码

线程在其生命周期中可以在不同状态之间转换。下面是一个Java线程状态转换图,它展示了各状态之间可能的转换路径:

  1. +--------------------+
  2. | NEW |
  3. +--------------------+
  4. |
  5. | start()
  6. v
  7. +--------------------+
  8. | RUNNABLE |<---+
  9. +--------------------+ |
  10. | ^ | |
  11. | | | | 获取到锁/唤醒/超时
  12. | | | |
  13. | | v |
  14. | +--------------------+
  15. | | BLOCKED |
  16. | +--------------------+
  17. |
  18. | 获取锁失败
  19. |
  20. |
  21. | wait()/join()
  22. v
  23. +--------------------+
  24. | WAITING |--------+
  25. +--------------------+ |
  26. | |
  27. | notify()/notifyAll() |
  28. | |
  29. v |
  30. +--------------------+ |
  31. | TIMED_WAITING |<-------+
  32. +--------------------+ wait(timeout)/
  33. | sleep(timeout)/
  34. | 超时 join(timeout)
  35. |
  36. v
  37. +--------------------+
  38. | TERMINATED |
  39. +--------------------+

让我们创建一个综合示例,展示线程在其生命周期中如何在不同状态之间转换:

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. import java.util.concurrent.TimeUnit;
  3. /**
  4. * 线程状态转换综合示例
  5. */
  6. public class ThreadStateTransitionDemo {
  7. // 共享锁对象
  8. private static final Object lock = new Object();
  9. // 标记是否应该等待
  10. private static boolean shouldWait = true;
  11. public static void main(String[] args) throws InterruptedException {
  12. // 创建线程,该线程会经历所有可能的状态
  13. Thread thread = new Thread(() -> {
  14. System.out.println("线程开始执行...");
  15. // 进入RUNNABLE状态的一些计算
  16. System.out.println("执行一些计算...");
  17. for (int i = 0; i < 1000000; i++) {
  18. Math.sqrt(i);
  19. }
  20. synchronized (lock) {
  21. // 进入条件等待
  22. if (shouldWait) {
  23. try {
  24. System.out.println("线程即将进入WAITING状态...");
  25. lock.wait(); // 这会导致线程进入WAITING状态
  26. System.out.println("线程从WAITING状态被唤醒!");
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. // 执行受锁保护的代码
  32. System.out.println("执行受锁保护的代码");
  33. }
  34. // 进入TIMED_WAITING状态
  35. try {
  36. System.out.println("线程即将进入TIMED_WAITING状态...");
  37. TimeUnit.SECONDS.sleep(2); // 休眠2秒,进入TIMED_WAITING状态
  38. System.out.println("线程从TIMED_WAITING状态恢复");
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. System.out.println("线程执行完毕,即将进入TERMINATED状态");
  43. });
  44. // 打印线程的初始状态(NEW)
  45. System.out.println("1. 初始状态: " + thread.getState());
  46. // 启动线程
  47. thread.start();
  48. System.out.println("2. 调用start()后: " + thread.getState());
  49. // 给线程一点时间运行
  50. TimeUnit.MILLISECONDS.sleep(100);
  51. System.out.println("3. 线程运行中: " + thread.getState());
  52. // 线程应该已经进入WAITING状态
  53. TimeUnit.SECONDS.sleep(1);
  54. System.out.println("4. 线程应该在等待: " + thread.getState());
  55. // 在锁上调用notify,唤醒等待的线程
  56. synchronized (lock) {
  57. System.out.println("主线程获取到锁,准备唤醒等待的线程");
  58. shouldWait = false;
  59. lock.notify();
  60. }
  61. // 给线程一点时间执行并进入TIMED_WAITING状态
  62. TimeUnit.MILLISECONDS.sleep(500);
  63. System.out.println("5. 线程应该在限时等待: " + thread.getState());
  64. // 等待线程执行完毕
  65. thread.join();
  66. System.out.println("6. 线程执行完毕: " + thread.getState());
  67. }
  68. }

运行这个示例,你将看到线程经历从NEW到RUNNABLE,然后到WAITING,然后回到RUNNABLE,再到TIMED_WAITING,最后到TERMINATED的完整生命周期过程。

📌 提示: 使用Thread.getState()方法可以获取线程的当前状态,但由于线程状态可能随时变化,因此获取到的状态可能已经过时。这个方法主要用于调试和监控。

3. 多线程执行障碍分析与解决

在多线程程序中,线程卡死(线程无法继续执行但也没有终止)是一种常见的问题。了解线程卡死的常见原因及其解决方案非常重要。

3.1 死锁(Deadlock)

问题描述: 死锁是指两个或多个线程互相等待对方持有的锁,导致所有线程都无法继续执行的情况。

示例代码:

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 死锁示例
  4. */
  5. public class DeadlockDemo {
  6. // 两个共享资源
  7. private static final Object resource1 = new Object();
  8. private static final Object resource2 = new Object();
  9. public static void main(String[] args) {
  10. // 创建第一个线程,先获取resource1,再获取resource2
  11. Thread thread1 = new Thread(() -> {
  12. System.out.println("线程1尝试获取resource1...");
  13. synchronized (resource1) {
  14. System.out.println("线程1获取到resource1");
  15. // 让线程休眠一会儿,确保线程2有时间获取resource2
  16. try {
  17. Thread.sleep(100);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println("线程1尝试获取resource2...");
  22. synchronized (resource2) {
  23. System.out.println("线程1同时获取到resource1和resource2");
  24. }
  25. }
  26. });
  27. // 创建第二个线程,先获取resource2,再获取resource1
  28. Thread thread2 = new Thread(() -> {
  29. System.out.println("线程2尝试获取resource2...");
  30. synchronized (resource2) {
  31. System.out.println("线程2获取到resource2");
  32. // 让线程休眠一会儿,确保线程1有时间获取resource1
  33. try {
  34. Thread.sleep(100);
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. System.out.println("线程2尝试获取resource1...");
  39. synchronized (resource1) {
  40. System.out.println("线程2同时获取到resource1和resource2");
  41. }
  42. }
  43. });
  44. // 启动线程
  45. thread1.start();
  46. thread2.start();
  47. }
  48. }

产生原因: 死锁通常由以下因素共同导致:

  1. 互斥:资源只能被一个线程占用
  2. 请求与保持:线程在等待资源时不释放已有资源
  3. 不可剥夺:资源只能由持有者自愿释放
  4. 循环等待:存在一个线程等待链

解决方案:

  1. 按顺序获取锁: 确保所有线程按照相同的顺序获取锁,可以避免循环等待
  2. 使用超时: 使用tryLock(timeout)等方法设置获取锁的超时时间
  3. 死锁检测: 使用工具(如JConsole、jstack)检测死锁
  4. 使用更高级的并发工具: 如java.util.concurrent包中的工具

3.2 活锁(Livelock)

问题描述: 活锁是指线程一直在运行,但是无法向前推进,通常是因为线程互相礼让导致的。

示例代码:

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 活锁示例
  4. */
  5. public class LivelockDemo {
  6. static class Worker {
  7. private String name;
  8. private boolean active;
  9. public Worker(String name, boolean active) {
  10. this.name = name;
  11. this.active = active;
  12. }
  13. public String getName() {
  14. return name;
  15. }
  16. public boolean isActive() {
  17. return active;
  18. }
  19. public void work(Worker otherWorker, Object commonResource) {
  20. while (active) {
  21. // 如果其他工作者处于活动状态,则礼让
  22. if (otherWorker.isActive()) {
  23. System.out.println(name + ": " + otherWorker.getName() + " 正在工作,我稍后再试");
  24. active = false;
  25. // 主动礼让CPU,让其他线程有机会运行
  26. Thread.yield();
  27. // 过一会儿,重新尝试工作
  28. try {
  29. Thread.sleep(100);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. active = true;
  34. continue;
  35. }
  36. // 如果其他工作者不活动,则使用共享资源
  37. System.out.println(name + ": 使用共享资源");
  38. active = false;
  39. // 工作完成,退出循环
  40. break;
  41. }
  42. }
  43. }
  44. public static void main(String[] args) {
  45. final Worker worker1 = new Worker("工作者1", true);
  46. final Worker worker2 = new Worker("工作者2", true);
  47. final Object commonResource = new Object();
  48. new Thread(() -> {
  49. worker1.work(worker2, commonResource);
  50. }).start();
  51. new Thread(() -> {
  52. worker2.work(worker1, commonResource);
  53. }).start();
  54. }
  55. }

产生原因: 活锁通常是由于线程过度谦让或反应过度调整导致的。

解决方案:

  1. 增加随机性: 在重试机制中加入随机延迟
  2. 优先级调整: 调整线程优先级,避免线程互相礼让
  3. 使用锁超时: 设置锁获取的超时机制

3.3 饥饿(Starvation)

问题描述: 饥饿是指线程因无法获取所需资源而长时间无法执行的情况。

示例代码:

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. /**
  3. * 线程饥饿示例
  4. */
  5. public class StarvationDemo {
  6. private static Object sharedResource = new Object();
  7. public static void main(String[] args) {
  8. // 创建5个高优先级线程,它们会反复获取共享资源
  9. for (int i = 0; i < 5; i++) {
  10. Thread highPriorityThread = new Thread(() -> {
  11. while (true) {
  12. synchronized (sharedResource) {
  13. // 高优先级线程持有锁时的操作
  14. try {
  15. Thread.sleep(200);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. });
  22. highPriorityThread.setPriority(Thread.MAX_PRIORITY);
  23. highPriorityThread.start();
  24. }
  25. // 创建一个低优先级线程,它很难获取到共享资源
  26. Thread lowPriorityThread = new Thread(() -> {
  27. while (true) {
  28. synchronized (sharedResource) {
  29. System.out.println("低优先级线程终于获取到了锁!");
  30. // 低优先级线程持有锁时的操作
  31. try {
  32. Thread.sleep(100);
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }
  38. });
  39. lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
  40. lowPriorityThread.start();
  41. }
  42. }

产生原因: 饥饿通常是由于资源分配不公平或线程优先级设置不合理导致的。

解决方案:

  1. 公平锁: 使用公平锁(如ReentrantLock(true))确保先到先得
  2. 合理设置优先级: 避免过大的优先级差异
  3. 资源限制: 限制高优先级线程对资源的占用时间

3.4 线程泄漏(Thread Leak)

问题描述: 线程泄漏是指创建的线程没有被正确终止或回收,导致系统资源被持续占用的情况。

示例代码:

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. /**
  5. * 线程泄漏示例
  6. */
  7. public class ThreadLeakDemo {
  8. public static void main(String[] args) {
  9. // 错误的线程池使用方式,没有关闭线程池
  10. badThreadPoolUsage();
  11. // 正确的线程池使用方式
  12. goodThreadPoolUsage();
  13. }
  14. private static void badThreadPoolUsage() {
  15. System.out.println("=== 错误的线程池使用方式 ===");
  16. // 创建一个固定大小的线程池
  17. ExecutorService executor = Executors.newFixedThreadPool(5);
  18. // 提交任务
  19. for (int i = 0; i < 10; i++) {
  20. final int taskId = i;
  21. executor.submit(() -> {
  22. System.out.println("执行任务 " + taskId);
  23. return taskId;
  24. });
  25. }
  26. // 没有调用shutdown()方法,线程池中的线程会一直存在
  27. System.out.println("任务提交完毕,但线程池没有被关闭");
  28. }
  29. private static void goodThreadPoolUsage() {
  30. System.out.println("=== 正确的线程池使用方式 ===");
  31. // 创建一个固定大小的线程池
  32. ExecutorService executor = Executors.newFixedThreadPool(5);
  33. try {
  34. // 提交任务
  35. for (int i = 0; i < 10; i++) {
  36. final int taskId = i;
  37. executor.submit(() -> {
  38. System.out.println("执行任务 " + taskId);
  39. return taskId;
  40. });
  41. }
  42. } finally {
  43. // 确保线程池被关闭
  44. executor.shutdown();
  45. System.out.println("任务提交完毕,线程池已关闭");
  46. }
  47. }
  48. }

产生原因: 线程泄漏通常是由于未正确关闭线程池、忘记等待线程结束或线程被卡在无限循环中导致的。

解决方案:

  1. 正确关闭线程池: 使用shutdown()shutdownNow()方法关闭线程池
  2. 使用try-finally块: 确保即使出现异常,资源也能被正确释放
  3. 设置超时机制: 为长时间运行的任务设置超时机制

4. 实战案例:线程状态监控工具

现在,让我们将所学知识应用到一个实际案例中。下面是一个简单的线程状态监控工具,可以定期打印指定线程的状态:

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. import java.util.Map;
  3. import java.util.concurrent.ConcurrentHashMap;
  4. import java.util.concurrent.Executors;
  5. import java.util.concurrent.ScheduledExecutorService;
  6. import java.util.concurrent.TimeUnit;
  7. /**
  8. * 线程状态监控工具
  9. */
  10. public class ThreadMonitor {
  11. // 存储要监控的线程
  12. private final Map<String, Thread> monitoredThreads = new ConcurrentHashMap<>();
  13. // 用于执行定期监控任务的调度器
  14. private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
  15. // 监控时间间隔(秒)
  16. private final int monitorInterval;
  17. // 是否正在监控
  18. private boolean monitoring = false;
  19. /**
  20. * 创建线程监控器
  21. * @param monitorInterval 监控间隔(秒)
  22. */
  23. public ThreadMonitor(int monitorInterval) {
  24. this.monitorInterval = monitorInterval;
  25. }
  26. /**
  27. * 添加要监控的线程
  28. * @param name 线程名称
  29. * @param thread 线程对象
  30. */
  31. public void addThread(String name, Thread thread) {
  32. monitoredThreads.put(name, thread);
  33. System.out.println("添加线程 '" + name + "' 到监控列表");
  34. }
  35. /**
  36. * 移除监控的线程
  37. * @param name 线程名称
  38. */
  39. public void removeThread(String name) {
  40. monitoredThreads.remove(name);
  41. System.out.println("从监控列表中移除线程 '" + name + "'");
  42. }
  43. /**
  44. * 开始监控
  45. */
  46. public void startMonitoring() {
  47. if (monitoring) {
  48. System.out.println("监控已经在运行中");
  49. return;
  50. }
  51. monitoring = true;
  52. // 创建并调度监控任务
  53. scheduler.scheduleAtFixedRate(() -> {
  54. System.out.println("\n=== 线程状态监控报告 ===");
  55. System.out.println("时间: " + System.currentTimeMillis());
  56. System.out.println("监控的线程数量: " + monitoredThreads.size());
  57. // 打印每个线程的状态
  58. monitoredThreads.forEach((name, thread) -> {
  59. Thread.State state = thread.getState();
  60. String statusInfo = String.format("线程 '%s' (ID: %d) - 状态: %s",
  61. name, thread.getId(), state);
  62. // 根据状态提供额外信息
  63. switch (state) {
  64. case BLOCKED:
  65. statusInfo += " - 等待获取监视器锁";
  66. break;
  67. case WAITING:
  68. statusInfo += " - 无限期等待另一个线程执行特定操作";
  69. break;
  70. case TIMED_WAITING:
  71. statusInfo += " - 等待另一个线程执行操作,最多等待指定的时间";
  72. break;
  73. case TERMINATED:
  74. statusInfo += " - 线程已结束执行";
  75. break;
  76. }
  77. System.out.println(statusInfo);
  78. });
  79. // 清理已终止的线程
  80. monitoredThreads.entrySet().removeIf(entry ->
  81. entry.getValue().getState() == Thread.State.TERMINATED);
  82. System.out.println("===========================");
  83. }, 0, monitorInterval, TimeUnit.SECONDS);
  84. System.out.println("开始监控线程状态,间隔: " + monitorInterval + " 秒");
  85. }
  86. /**
  87. * 停止监控
  88. */
  89. public void stopMonitoring() {
  90. monitoring = false;
  91. scheduler.shutdown();
  92. System.out.println("停止线程状态监控");
  93. }
  94. }

现在,让我们使用这个监控工具来监控不同状态的线程:

  1. package org.devlive.tutorial.multithreading.chapter02;
  2. import java.util.concurrent.CountDownLatch;
  3. import java.util.concurrent.TimeUnit;
  4. /**
  5. * 线程监控工具使用示例
  6. */
  7. public class ThreadMonitorDemo {
  8. // 共享锁对象
  9. private static final Object lock = new Object();
  10. // 用于协调线程的CountDownLatch
  11. private static final CountDownLatch latch = new CountDownLatch(1);
  12. public static void main(String[] args) {
  13. // 创建线程监控器,每2秒监控一次
  14. ThreadMonitor monitor = new ThreadMonitor(2);
  15. // 创建几个用于演示不同状态的线程
  16. // 1. 创建一个长时间运行的线程
  17. Thread runningThread = new Thread(() -> {
  18. System.out.println("长时间运行的线程开始执行");
  19. long sum = 0;
  20. for (long i = 0; i < 10_000_000_000L; i++) {
  21. sum += i;
  22. if (i % 1_000_000_000 == 0) {
  23. System.out.println("计算中: " + i / 1_000_000_000);
  24. }
  25. }
  26. System.out.println("长时间运行的线程计算结果: " + sum);
  27. });
  28. // 2. 创建一个会进入WAITING状态的线程
  29. Thread waitingThread = new Thread(() -> {
  30. try {
  31. System.out.println("等待线程开始执行");
  32. latch.await(); // 等待主线程计数器减为0
  33. System.out.println("等待线程继续执行");
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. });
  38. // 3. 创建一个会进入TIMED_WAITING状态的线程
  39. Thread sleepingThread = new Thread(() -> {
  40. try {
  41. System.out.println("休眠线程开始执行");
  42. Thread.sleep(20000); // 休眠20秒
  43. System.out.println("休眠线程醒来继续执行");
  44. } catch (InterruptedException e) {
  45. e.printStackTrace();
  46. }
  47. });
  48. // 4. 创建一个会进入BLOCKED状态的线程
  49. Thread blockedThread = new Thread(() -> {
  50. System.out.println("阻塞线程开始执行,等待获取锁");
  51. synchronized (lock) {
  52. System.out.println("阻塞线程获取到锁");
  53. }
  54. });
  55. // 主线程先获取锁,让blockedThread进入BLOCKED状态
  56. synchronized (lock) {
  57. // 添加线程到监控器
  58. monitor.addThread("运行线程", runningThread);
  59. monitor.addThread("等待线程", waitingThread);
  60. monitor.addThread("休眠线程", sleepingThread);
  61. monitor.addThread("阻塞线程", blockedThread);
  62. // 启动线程
  63. runningThread.start();
  64. waitingThread.start();
  65. sleepingThread.start();
  66. blockedThread.start();
  67. // 开始监控
  68. monitor.startMonitoring();
  69. // 主线程持有锁10秒钟,让blockedThread保持BLOCKED状态
  70. try {
  71. System.out.println("主线程持有锁10秒钟,blockedThread将保持BLOCKED状态");
  72. TimeUnit.SECONDS.sleep(10);
  73. } catch (InterruptedException e) {
  74. e.printStackTrace();
  75. }
  76. System.out.println("主线程释放锁,blockedThread将获取锁并继续执行");
  77. }
  78. // 5秒后释放等待线程
  79. try {
  80. TimeUnit.SECONDS.sleep(5);
  81. System.out.println("主线程释放等待线程");
  82. latch.countDown();
  83. } catch (InterruptedException e) {
  84. e.printStackTrace();
  85. }
  86. // 等待所有线程结束
  87. try {
  88. runningThread.join();
  89. waitingThread.join();
  90. sleepingThread.join();
  91. blockedThread.join();
  92. } catch (InterruptedException e) {
  93. e.printStackTrace();
  94. }
  95. // 停止监控
  96. monitor.stopMonitoring();
  97. }
  98. }

这个实战案例展示了如何创建一个工具来监控线程的状态变化,这对于调试复杂的多线程应用非常有用。

常见问题与解决方案

问题1:如何正确终止一个线程?

问题描述: Java中没有安全的方法可以直接强制终止一个线程。Thread.stop()Thread.suspend()Thread.resume()方法已被弃用,因为它们可能导致数据不一致。

解决方案: 使用中断机制实现线程的协作终止:

  1. public class GracefulThreadTermination {
  2. public static void main(String[] args) throws InterruptedException {
  3. // 创建一个能响应中断的线程
  4. Thread thread = new Thread(() -> {
  5. // 检查中断标志
  6. while (!Thread.currentThread().isInterrupted()) {
  7. try {
  8. // 执行任务...
  9. System.out.println("线程执行中...");
  10. Thread.sleep(1000);
  11. } catch (InterruptedException e) {
  12. // 捕获中断异常,重新设置中断标志,并退出循环
  13. System.out.println("线程被中断,准备清理资源并退出");
  14. Thread.currentThread().interrupt();
  15. break;
  16. }
  17. }
  18. // 线程结束前的清理工作
  19. System.out.println("线程正在清理资源...");
  20. System.out.println("线程已安全终止");
  21. });
  22. thread.start();
  23. // 主线程休眠3秒
  24. Thread.sleep(3000);
  25. // 中断线程
  26. System.out.println("主线程发送中断信号");
  27. thread.interrupt();
  28. }
  29. }

问题2:如何解决死锁?

问题描述: 死锁是多线程编程中的常见问题,一旦发生很难恢复。

解决方案:

  1. 预防死锁: 按固定顺序获取锁

    1. // 正确的获取多个锁的方式
    2. public void transferMoney(Account fromAccount, Account toAccount, double amount) {
    3. // 确保按账户ID的顺序获取锁
    4. Account firstLock = fromAccount.getId() < toAccount.getId() ? fromAccount : toAccount;
    5. Account secondLock = fromAccount.getId() < toAccount.getId() ? toAccount : fromAccount;
    6. synchronized (firstLock) {
    7. synchronized (secondLock) {
    8. // 执行转账逻辑
    9. }
    10. }
    11. }
  2. 使用tryLock带超时的锁获取:

    1. Lock lock1 = new ReentrantLock();
    2. Lock lock2 = new ReentrantLock();
    3. public void doSomething() {
    4. boolean gotFirstLock = false;
    5. boolean gotSecondLock = false;
    6. try {
    7. gotFirstLock = lock1.tryLock(1, TimeUnit.SECONDS);
    8. if (gotFirstLock) {
    9. gotSecondLock = lock2.tryLock(1, TimeUnit.SECONDS);
    10. }
    11. if (gotFirstLock && gotSecondLock) {
    12. // 成功获取两个锁,执行操作
    13. } else {
    14. // 无法获取锁,放弃本次操作
    15. }
    16. } catch (InterruptedException e) {
    17. Thread.currentThread().interrupt();
    18. } finally {
    19. if (gotSecondLock) lock2.unlock();
    20. if (gotFirstLock) lock1.unlock();
    21. }
    22. }

问题3:如何检测和解决线程泄漏?

问题描述: 线程泄漏会导致系统资源不断被消耗,最终可能导致OutOfMemoryError。

解决方案:

  1. 使用线程池: 而不是直接创建线程
  2. 始终关闭线程池: 使用try-finally确保关闭
  3. 设置合理的线程池大小: 避免创建过多线程
  4. 监控线程数量: 定期检查应用程序的线程数量
  1. // 正确使用线程池的方式
  2. ExecutorService executor = null;
  3. try {
  4. executor = Executors.newFixedThreadPool(10);
  5. // 提交任务到线程池
  6. for (int i = 0; i < 100; i++) {
  7. executor.submit(new Task(i));
  8. }
  9. } finally {
  10. // 确保线程池被关闭
  11. if (executor != null) {
  12. executor.shutdown();
  13. try {
  14. // 等待所有任务完成,但最多等待60秒
  15. if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
  16. // 如果等待超时,强制关闭
  17. executor.shutdownNow();
  18. }
  19. } catch (InterruptedException e) {
  20. // 如果当前线程被中断,重新中断,并强制关闭线程池
  21. Thread.currentThread().interrupt();
  22. executor.shutdownNow();
  23. }
  24. }
  25. }

小结

在本章中,我们深入学习了Java线程的生命周期和状态转换:

  1. 线程的六大状态: 我们详细了解了Java线程的六种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED)及其特点。

  2. 状态转换机制: 我们通过代码示例和状态转换图,理解了线程如何在不同状态之间转换,以及哪些方法会触发这些转换。

  3. 线程卡死问题: 我们学习了几种常见的线程卡死情况(死锁、活锁、饥饿和线程泄漏)及其解决方案。

  4. 实战应用: 我们创建了一个线程状态监控工具,可以实时监控线程状态的变化,这对调试多线程应用非常有用。

理解线程的生命周期对于开发高效、可靠的多线程应用至关重要。通过掌握线程状态转换的规律和可能出现的问题,你可以更好地设计和调试多线程程序。

在下一章中,我们将学习线程的基本操作,包括启动、休眠、中断、连接等,进一步增强你的多线程编程技能。

本章节源代码地址为 https://github.com/qianmoQ/tutorial/tree/main/java-multithreading-tutorial/src/main/java/org/devlive/tutorial/multithreading/chapter02