细说Java多线程系列(1.2)线程状态及其切换

  • Post author:
  • Post category:java




前言

上次说了与线程有关的概念,以及创建线程的方式,今天我们来聊一聊线程的状态与切换。

注:使用的jdk版本为11.0.6



线程的状态

众所周知,线程是有状态的。这里要注意将

Java

中的线程状态与

操作系统

中的线程状态进行区分。很多文章都将Java中的线程与操作系统中的线程混为一谈,容易让人产生“Java线程与操作系统线程是一样的”这种错误认识。实际上,虽然Java中的进程最后也会落到操作系统层面上实现,但是两者设计的出发点有着根本不同。



操作系统中的线程

就以线程的状态来说,Java源码中定义的线程状态有六种;但是在操作系统中,由于线程被视为轻量级进程,因此操作系统的线程状态与进程状态是一致的。

注:进程的状态有五种:新生状态(New)、终止状态(Exit或者叫Terminated)、执行状态(Running)、就绪状态(Ready)、阻塞状态(Blocked)。而线程的状态我在书上没有看到有特别明确的说明,网上的资料中也有很多将线程的Blocked状态叫成Waiting的,表达的都是一个意思。

2.1

以上图为例子,抛去New和Terminated不谈,在操作系统中,一个创建好的线程自然而然进入Ready(就绪)状态,表示万事俱备,只等待CPU的调度;而Running(运行)状态则表示线程正在被CPU调度中,调度结束(时间片用完)后,如果任务结束就进入Terminated状态,否则回到Ready状态。Waiting(等待)状态则表示线程在等待某些资源(比如IO资源),在获得这些资源之前调度它也没用,只有获得之后才会转入Ready状态等待调度。

可以发现,操作系统中的线程状态,是基于CPU来定义的。只有将会被CPU调度、正在被CPU调度、不会被CPU调度三种状态。不会被CPU调度的线程一律打为Waiting,但是Java中的划分却不是这样。



Java中的线程

我们查看Java源码,可以看到Java中的线程一共有六种状态:

2.2



New

源码中的注释:

/**
 * Thread state for a thread which has not yet started.
 * 一种“尚未启动”的线程状态
 */

New:指的是此时线程已经被创建出来了,但是还没有调用线程的start()方法,因此还没有启动。

 Thread thread4 = new Thread(()->{
     for(int i = 0;i < 10;i ++){
         System.out.println("lambda:" + Thread.currentThread().getName());
     }
 });
// thread4.start();
//打印出来的状态为NEW
 System.out.println("state: " + thread4.getState());


Runnable

源码中的注释:

/**
 * Thread state for a runnable thread.  A thread in the runnable
 * state is executing in the Java virtual machine but it may
 * be waiting for other resources from the operating system
 * such as processor.
 * 一种“可运行”的线程状态
 * 一个处于“可运行”状态中的线程正在Java虚拟机中执行着,但是它可能在等待来自操作系统的其他资源,比如处理器
 */

Runnable:此时线程正在

Java虚拟机

中运行。

注释里表达得很清楚了:在Java虚拟机中运行,并不一定表示在操作系统中也正运行。Runnable(可运行)是针对Java虚拟机来说的;一个Runnable的线程,到了操作系统层面,可能是Running(CPU调度中),也可能是Ready(等待CPU调度),甚至可能是Waiting(等待IO资源)。

Java提供了一个yield()方法用于让出时间片,这样线程可以从Running状态变为Ready状态;因此一般谈到Runnable状态时,常常把它分成两个小状态:Running和Ready,但是

Java中并没有这两个状态!!!



Blocked

源码中的注释:

/**
 * Thread state for a thread blocked waiting for a monitor lock.
 * 一种“线程正阻塞着等待一个监视器的锁”的线程状态(即阻塞状态)
 * A thread in the blocked state is waiting for a monitor lock
 * to enter a synchronized block/method or
 * reenter a synchronized block/method after calling
 * {@link Object#wait() Object.wait}.
 * 一个线程处于“阻塞”状态,是正在等着一个监视器的锁,以进入一个同步代码块/方法,
 * 或者在其调用了Object.wait()方法后,重新进入一个同步代码块/方法
 */

Blocked:此时线程正在等待监视器的锁,以进入synchronized代码块或方法

通过注释可以发现,Blocked状态是与synchronized关键字息息相关的一种状态。Java中还有其他的锁,其原理与synchronized完全不同,因此调用其他锁时,虽然分析上说线程是“阻塞”的,但是实际上并没有进入Blocked状态。也就是说,我们平时讨论的“阻塞”,是相对于锁来说,而Java线程的“阻塞状态”,仅限于synchronized的锁。



Waiting

源码中的注释:

/**
 * Thread state for a waiting thread.
 * 一种“等待中”的线程状态
 * A thread is in the waiting state due to calling one of the
 * following methods:
 * <ul>
 *   <li>{@link Object#wait() Object.wait} with no timeout</li>
 *   <li>{@link #join() Thread.join} with no timeout</li>
 *   <li>{@link LockSupport#park() LockSupport.park}</li>
 * </ul>
 * 一个线程处于“等待中”状态,是由于调用了以下方法之一:
 * 1.没有时间限制的 Object.wait() 2. 没有时间限制的 Thread.join() 3.LockSupport.park()
 *
 * <p>A thread in the waiting state is waiting for another thread to
 * perform a particular action.
 *
 * For example, a thread that has called {@code Object.wait()}
 * on an object is waiting for another thread to call
 * {@code Object.notify()} or {@code Object.notifyAll()} on
 * that object. A thread that has called {@code Thread.join()}
 * is waiting for a specified thread to terminate.
 * 一个处于“等待中”状态的线程正在等着其他线程完成特定操作
 * 例如,一个线程调用了某个对象的Object.wait()方法,
 * 意味着它在等待另一个线程调用同一个对象的Object.notify()或者Objcet.notifyAll()方法
 * 一个线程调用了Thread.join()方法,意味着它在等待这个(插入进来的)线程终止。
 */

Waiting:此时线程正在等待其他线程做出特定操作。

可以看到,只有三种情况会进入Waiting状态,对应不同的情况,线程所等待的特定操作也不同

  1. 调用没有时间限制的 Object.wait():等待其他线程调用同一对象的Object.notify()或Objcet.notifyAll()
  2. 没有时间限制的 Thread.join():等待插入进来的线程终止
  3. LockSupport.park():等待获取到LockSupport.unpark()对本线程的许可


Timed_Waiting

源码中的注释:

/**
 * Thread state for a waiting thread with a specified waiting time.
 * A thread is in the timed waiting state due to calling one of
 * the following methods with a specified positive waiting time:
 * <ul>
 *   <li>{@link #sleep Thread.sleep}</li>
 *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
 *   <li>{@link #join(long) Thread.join} with timeout</li>
 *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
 *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
 * </ul>
 * 一种“在指定时间内等待”的线程状态(即超时等待)
 * 一个线程处于“超时等待”状态,是由于调用了以下指定等待时间的方法:
 * 1.Thread.sleep() 2.有时间限制的Object.wait() 3.有时间限制的Thread.join() 
 * 4.LockSupport.parkNanos() 5.LockSupport.parkUntil()
 */

Timed_Waiting:此时线程在等待指定时间结束

有五种情况会进入到Timed_Waiting:

  1. Thread.sleep():等待一定时间后结束Timed_Waiting状态
  2. 有时间限制的Object.wait():等待一定时间后结束,或者wait()方法所等待的特定操作出现了,也会结束
  3. 有时间限制的Thread.join():等待一定时间后结束,或者join()方法所等待的特定操作出现了,也会结束
  4. LockSupport.parkNanos():等待一定时间后结束,时间单位为纳秒
  5. LockSupport.parkUntil():等待到某个时间后结束,时间单位为毫秒


Terminated

源码中的注释:

/**
 * Thread state for a terminated thread.
 * 一种“已终止”的线程状态
 * The thread has completed execution.
 * 线程已经完成了执行
 */

Terminated:此时线程已经执行完毕

可以发现,Java中的线程状态,是针对于JVM,或者说系统资源来定义的。对于JVM来说,只要有系统资源(CPU、IO资源等)在为线程服务,线程就是运行着的。处于非运行状态的线程,Java做了更详细的划分:

  1. 等待synchronized的锁:Blocked
  2. 不等待synchronized的锁,且没有时间限制:Waiting
  3. 不等待synchronized的锁,有时间限制:Timed_Waiting



线程状态的切换

读完上面的内容,相信大家对Java线程的各个状态都了解了,记住线程状态怎么切换自然也不难。

为了展示得更清晰,给大家画了一个图:

2.3

网上还有一些示意图,将调用Object.notify()/Objcet.notifyAll()之后的箭头直接指向了Blocked状态,这是因为Object.wait()方法必须在synchronized代码/方法中调用(wait方法的目的就是释放监视器的锁,这当然要先获得一个监视器的锁);这意味着在Object.notify()/Objcet.notifyAll()之后,线程仍要去尝试获取锁,线程有可能会变为Blocked状态。

我猜想应该先转为Runnable去尝试获取锁,如果直接获取到了自然不会Blocked了。但是也有不同的说法,还需要去查查别的资料。

至于这些线程相关的方法,留到下一篇再说。

参考资料:


漫谈操作系统9 – 线程运行状态


Java线程和操作系统线程的关系


操作系统层面和jvm层面的线程状态区别


Java中线程状态与IO的关系


操作系统层面和jvm层面的线程状态区别


Java中线程状态与IO的关系


Java线程状态



版权声明:本文为weixin_42643216原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。