Java 线程状态及其转换机制 | 周末学习

这是我参与更文挑战的第4天,活动详情查看:更文挑战

本文已参与周末学习计划,点击查看详情。

  Java定义了6种线程状态,在任意时间点,每一个线程只能是其中的一种状态;并且对于单核CPU,任意时间点,只能存在一个线程为运行状态。线程状态通过特定的机制和方法进行转换。

1 Java线程的6种状态

  线程状态的本质是 Thread 类中的一个变量,叫 private volatile int threadStatus = 0; 。而这个变量的值通过 Thread 内部的一个枚举类定义。

public enum State {
        /**
         * 新建状态
         * 创建后尚未启动的线程
         */
        NEW,

        /**
         * 运行状态
         * 包括正在执行,也可能正在等待操作系统为它分配执行时间
         */
        RUNNABLE,

        /**
         * 阻塞状态
         * 一个线程因为等待临界区的锁被阻塞产生的状态
         */
        BLOCKED,

        /**
         * 无限期等待状态
         * 线程不会被分配处理器执行时间,需要等待其他线程显式唤醒
         */
        WAITING,

        /**
         * 限期等待状态
         * 线程不会被分配处理器执行时间,但也无需等待被其他线程显式唤醒
         * 在一定时间之后,它们会由操作系统自动唤醒
         */
        TIMED_WAITING,

        /**
         * 结束状态
         * 线程退出或已经执行完成
         */
        TERMINATED;
    }
复制代码

2 无中断的线程状态转换

  正常一个线程,最简单的生命周期都应该经历这三个过程:**初始 –> 运行 –> 终止 **。

初始(NEW

  • 当一个线程对象被创建出来时,线程并没有立即启动,而是将threadStatus变量初始化为 NEW 状态。

运行(RUNNABLE

  • 我们都知道,启动一个线程的方法是调用Thread的 start() 方法
  • 调用 start() 方法后,最终会调用一个本地方法 private native void start0();
  • 在JVM中, start0() 方法最终会调用到操作系统创建线程的方法
  • 在调用 Thread.start()之后,线程的状态将会变为 RUNNABLE 状态
  • 在单核CPU中,同一时刻,只能运行一个线程,具体启动哪个线程,由操作系统调度。在JVM层面,虽然线程为 RUNNABLE 状态,但不代表就是运行中,只能说得到了随时运行的机会。而在操作系统层面看,处于可运行状态的线程都会在一个就绪队列中等待调度,最终进入CPU运行的才是真正的RUNNING,仍处于就绪队列中的都叫READY
  • Java没有区分RUNNINGREADY,将这两种都叫RUNNABLE

终止(TERMINATED

  • 当一个线程执行完毕,线程的状态就变为 TERMINATED
  • 处于 TERMINATED 状态的线程都无法死灰复燃,如果强行再启动线程,将会抛出 java.lang.IllegalThreadStateException 异常。

最简单的线程生命周期.png

3 线程阻塞

  当两个并发线程访问同一个对象的synchronized 代码块时,获得锁的线程会进入RUNNABLE 状态;而另一个线程将进入 BLOCKED 状态,知道下一次获得对象锁才能再一次进入 RUNNABLE 状态。

  当多个并发线程争抢同一个对象锁时,获得锁的线程会进入RUNNABLE 状态;其他所有对象都将进入 BLOCKED 状态,并进入锁对象的同步队列中;当持有锁的这个线程,释放了锁之后,会唤醒该锁对象同步队列中的所有线程,这些线程会继续尝试抢锁。如此往复。

发生阻塞的线程生命周期.png

4 wait() 、 join() 和 park()

  当使用 wait()join()park() 这三个方法时,都会使线程进入WAITING 状态。这三个方法的使用会有一定的区别。

wait()

  • wait() 方法是运行中的线程主动调用的
  • 当运行中的线程调用wait() 之后,会释放锁对象,线程状态变成WAITING,线程进入锁对象的等待对列
  • 当另一个获得锁对象的线程调用 notify/notifyAll 方法时,等待队列 中的线程才会被唤醒
  • 被唤醒的线程争抢对象锁,获得锁的进入RUNNABLE状态,没有获得锁的进入BLOCKED 状态,并进入锁对象的同步队列

join()

  • 当其他线程调用 join() 方法时,正在运行的线程会被迫退出 RUNNABLE 状态,进入 WAITING
  • join() 底层仍然是通过调用wait(),由正在运行的线程调用wait() 方法,锁对象是线程本身
  • 使用join() 方法插队后进入运行状态的线程,在执行完毕会自动调用notifyAll() 方法

park()

  • 一个线程调用LockSupport.park() 方法,该线程状态会从 RUNNABLE 变成 WAITING
  • 另一个线程调用 LockSupport.unpark(Thread 刚刚的线程),刚刚的线程会从 WAITING 回到 RUNNABLE

线程等待机制.png

5.TIMED_WAITING

  这部分就再简单不过了,将上面导致线程变成 WAITING 状态的那些方法,都增加一个超时参数,就变成了将线程变成 TIMED_WAITING 状态的方法了。

线程状态转换.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享