线程的状态

线程分为 6 种状态,可以由 Thread.State 得知,总结如下:

  • NEW
    • 表示线程被创建出来还没真正启动的状态,可以认为它是个 Java 内部状态
  • RUNNABLE
    • 表示该线程已经在 JVM 中执行,当然由于执行需要计算资源,它可能是正在运行,也可能还在等待系统分配给它 CPU 片段,在就绪队列里面排队
  • BLOCKED
    • 阻塞表示线程在等待 Monitor lock。比如,线程试图通过 synchronized 去获取某个锁,但是其他线程已经独占了,那么当前线程就会处于阻塞状态。
  • WAITING
    • 表示正在等待其他线程采取某些操作。一个常见的场景是类似生产者消费者模式,发现任务条件尚未满足,就让当前消费者线程等待(wait),另外的生产者线程去准备任务数据,然后通过类似 notify 等动作,通知消费线程可以继续工作了。Thread.join() 也会令线程进入等待状态。
  • TIME_WAITING
    • 其进入条件和等待状态类似,但是调用的是存在超时条件的方法,比如 wait 或 join 等方法的指定超时版本
  • TERMINATED
    • 不管是意外退出还是正常执行结束,线程已经完成使命,终止运行,也有人把这个状态叫作死亡

下面是这 6 种状态的例子

  1. public class ThreadMain {
  2. public static void main(String[] args) {
  3. TaskQueue queue = new TaskQueue();
  4. final Thread task = new Thread(() -> {
  5. try {
  6. String s = queue.getTask();
  7. System.out.println("任务线程 1 拿到了任务 " + s);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }, "任务线程 1");
  12. final Thread task2 = new Thread(() -> {
  13. try {
  14. Thread.sleep(100);
  15. String s = queue.getTask();
  16. System.out.println("任务线程 2 拿到了任务 " + s);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }, "任务线程 2");
  21. print(task,1);
  22. task.start();
  23. print(task,2);
  24. task2.start();
  25. Thread putTask = new Thread(() -> {
  26. print(task,3);
  27. print(task2,4);
  28. try {
  29. Thread.sleep(200);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. print(task2,5);
  34. queue.addTask("S 级任务");
  35. try {
  36. Thread.sleep(1);
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. print(task2,6);
  41. }, "生产线程 1");
  42. putTask.start();
  43. Thread putTask2 = new Thread(()->{
  44. queue.addTask("A 级任务");
  45. });
  46. putTask2.start();
  47. }
  48. static final Object lock = new Object();
  49. static class TaskQueue {
  50. Queue<String> queue = new LinkedList<>();
  51. public void addTask(String s) {
  52. synchronized (lock) {
  53. try {
  54. Thread.sleep(500);
  55. } catch (InterruptedException e) {
  56. e.printStackTrace();
  57. }
  58. queue.add(s);
  59. lock.notifyAll();
  60. }
  61. }
  62. public String getTask() throws InterruptedException {
  63. synchronized (lock) {
  64. while (queue.isEmpty()) {
  65. lock.wait();
  66. }
  67. return queue.remove();
  68. }
  69. }
  70. }
  71. static void print(Thread thread,int i) {
  72. System.out.println(i+" "+thread.getName() + " " + thread.getState());
  73. }
  74. }

得到了如下输出:

  1. 1 任务线程 1 NEW
  2. 2 任务线程 1 RUNNABLE
  3. 3 任务线程 1 WAITING
  4. 4 任务线程 2 TIMED_WAITING
  5. 5 任务线程 2 BLOCKED
  6. 任务线程 1 拿到了任务 A 级任务
  7. 任务线程 2 拿到了任务 S 级任务
  8. 6 任务线程 2 TERMINATED

简单分下 1,2 输出代表着线程的创建和运行状态,这没啥好说的。
3 行输出是指任务线程 1 在执行 getTask 方法时执行了 wait,所以变成了 WAITING 状态。
4 行输出是由于任务线程 2 在执行睡眠 100 的操作,所以是 TIMED_WAITING 状态。
5 行输出是指 任务线程 2 要执行 getTask 方法时,发现这个锁正在被 addTask 方法持有(putTask2线程执行的),所以就是 BLOCKED 状态
剩下的输出就比较直观简单,就不分析了

上边的例子使用了 Object 提供的 wait/notifyAll 方法去更改线程的状态,如果我们持有某个对象的 Monitor 锁,调用 wait 会让线程处于 WAITING 状态,直到其他线程 notify 或者 notifyAll。
线程本身的 join 方法也会让线程处于 WAITING 状态等待线程结束。yield 是告诉调度器主动让出 CPU,下面是线程状态切换总结的一张图
image.png

wait/notify/notifyAll 看起来很简单,但是在实际应用中非常的晦涩、易错,所以在大多数情况下我们已经不再去使用了。作为替代的就是 Java 后来引入的并发包

上边的例子其实也算介绍了间接 Object 类里的 wait/notifyAll,Thread 里也有 yield 与 join 方法比较容易搞混,下面也去举个例子说明下

  • yield:让出此次线程调度,此操作并不能保证其他线程会抢到执行权,有可能又被这个线程抢走了
  • join:等待线程结束,调用 join 后,线程会进入到等待状态

并发包总结

这个有点多,参考下面的博客
语雀内容