2015年4月

Java thread 线程的6种状态及转换

Java 线程共有6种状态:

  1. NEW 一个线程刚创建, 尚未开始运行. 这种状态不会出现在 thread dump 种. 比如代码中 new 了一个 Thread(), 还没调用 start() 方法, 就是 NEW 这个状态.
  2. RUNNABLE 线程正在运行或可运行状态,但是被挂起. 线程运行都是基于时间片的, 只要线程属于可运行状态(RUNNABLE), 就会给它分配时间片, 一旦时间片分配到该线程, 它就立马能运行.
  3. BLOCKED 线程正在等待一个 monitor 锁. 它处于该锁的 block 队列里面. 这个完全是跟 java 的 synchronize 关键字有关, 在线程等待进入 synchronize 里面的同步区之前, 尚未获得锁的时候, 就处于 BLOCKED 状态. 也只有这种情况, 线程才能进入 BLOCKED 状态;
  4. WAITING 线程正在等待其它线程触发某种操作, 在其它线程没有触发某种操作之前, 它只能在这个 WAITING 状态等待;
  5. TIMED_WAITING 同上面的 WAITING 状态, 只不过它有最长等待时间, 一旦时间到, 它就退出这种 TIMED_WAITING 状态;
  6. TERMINATED 线程以及结束. 这种状态也不可能出现在 thread dump 当中.

    其中 NEW 和 TERMINATED 都很容易理解, 一个是开始之前, 一个是结束之后, 都不会出现在 thread dump 中. RUNNABLE 表示线程处在正在运行或随时可运行状态, 只要给它分配到时间片, 它立马就能运行. 所以 RUNNABLE 状态并不代表它正在运行.

    只有当一个线程想要进入 Synchronize 保护的代码区时候, 才有可能进入 BLOCKED 状态. 如果它获得了 monitor 锁, 那么它直接进入被保护的代码块, 还是 RUNNABLE 状态. 如果没获得,那么进入该 monitor 的 block 队列(Entry Set), 线程则变成 BLOCKED 状态. 另外一个很容易忽略的地方是,当一个线程曾经拥有 monitor 锁, 然后因为某种原因需要挂起(已经进入被 synchronize 保护的代码区, 由于某种条件不满足, 不能退出被保护的代码区, 只能挂起当前线程), 这时该线程调用 Object.wait() 方法, 线程变成 WAITING 状态, 这时线程进入该 monitor 的 wait 队列 (Wait Set), 如果有其它线程调用该对象的 notify/notifyAll 方法, 那么这些线程被从 wait 队列 移入 block 队列( Entry Set), 这时该线程又要重新竞争 monitor 锁, 这时线程状态变化为 BLOCKED 状态. 所以不论是直接竞争锁未得到锁, 还是因为之前由于 wait 进入 Wait Set, 后来被 notify 之后, 重新进入 Entry Set 等待竞争monitor 锁, 只有这2种情况, 线程会进入 BLOCKED 状态.

    对于 WAITING 状态, 只有下面这几种情况, 线程才可能进入 WAITING 状态:

    1. Object.wait with no timeout
    2. Thread.join with no timeout
    3. LockSupport.park

      通过 Object.wait 进入的线程, 进入该 monitor 锁的 Wait Set, 等待该 Object 的 notify/notifyAll 事件;

      通过 Thread.join 进入的线程, 等待另外一个线程的结束. 实际的实现还是通过 synchronize 的 monitor 锁和 wait 方法实现的. 假如有线程 threadA 和 threadB 两个线程, threadA 通过调用 threadB.join() 方法等待 threadB 的结束. 实际实现是 threadB 对象的 join 方法是 synchronized, 它先是获得了 threadB 对象的 monitor 锁, 然后又进入了该 monitor 的 wait() 方法, 进入 Wait Set, 进入等待状态. 当 threadB 结束运行之前, 它会调用 threadB 对象的 notifyAll() 方法, 那么所有在 Wait Set 的线程进入 Entry Set, 之后竞争 threadB 对象的 monitor 锁, 然后释放锁, 继续执行, 最终通过这种方式实现对其它线程的终止状态的等待.

      通过 LockSupport.park 进入的线程, 一般都是通过 AQS 进入的, 它们都在等待 LockSupport.unpark() 方法.

      对于 TIMED_WAITING 状态, 下面这几种情况会进入该状态:

    4. Thread.sleep
    5. Object.wait with timeout
    6. Thread.join with timeout
    7. LockSupport.parkNanos
    8. LockSupport.parkUntil