细说Java多线程系列(1.2)线程状态及其切换
前言
上次说了与线程有关的概念,以及创建线程的方式,今天我们来聊一聊线程的状态与切换。
注:使用的jdk版本为11.0.6
线程的状态
众所周知,线程是有状态的。这里要注意将
Java
中的线程状态与
操作系统
中的线程状态进行区分。很多文章都将Java中的线程与操作系统中的线程混为一谈,容易让人产生“Java线程与操作系统线程是一样的”这种错误认识。实际上,虽然Java中的进程最后也会落到操作系统层面上实现,但是两者设计的出发点有着根本不同。
操作系统中的线程
就以线程的状态来说,Java源码中定义的线程状态有六种;但是在操作系统中,由于线程被视为轻量级进程,因此操作系统的线程状态与进程状态是一致的。
注:进程的状态有五种:新生状态(New)、终止状态(Exit或者叫Terminated)、执行状态(Running)、就绪状态(Ready)、阻塞状态(Blocked)。而线程的状态我在书上没有看到有特别明确的说明,网上的资料中也有很多将线程的Blocked状态叫成Waiting的,表达的都是一个意思。
以上图为例子,抛去New和Terminated不谈,在操作系统中,一个创建好的线程自然而然进入Ready(就绪)状态,表示万事俱备,只等待CPU的调度;而Running(运行)状态则表示线程正在被CPU调度中,调度结束(时间片用完)后,如果任务结束就进入Terminated状态,否则回到Ready状态。Waiting(等待)状态则表示线程在等待某些资源(比如IO资源),在获得这些资源之前调度它也没用,只有获得之后才会转入Ready状态等待调度。
可以发现,操作系统中的线程状态,是基于CPU来定义的。只有将会被CPU调度、正在被CPU调度、不会被CPU调度三种状态。不会被CPU调度的线程一律打为Waiting,但是Java中的划分却不是这样。
Java中的线程
我们查看Java源码,可以看到Java中的线程一共有六种状态:
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状态,对应不同的情况,线程所等待的特定操作也不同
- 调用没有时间限制的 Object.wait():等待其他线程调用同一对象的Object.notify()或Objcet.notifyAll()
- 没有时间限制的 Thread.join():等待插入进来的线程终止
- 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:
- Thread.sleep():等待一定时间后结束Timed_Waiting状态
- 有时间限制的Object.wait():等待一定时间后结束,或者wait()方法所等待的特定操作出现了,也会结束
- 有时间限制的Thread.join():等待一定时间后结束,或者join()方法所等待的特定操作出现了,也会结束
- LockSupport.parkNanos():等待一定时间后结束,时间单位为纳秒
- LockSupport.parkUntil():等待到某个时间后结束,时间单位为毫秒
Terminated
源码中的注释:
/**
* Thread state for a terminated thread.
* 一种“已终止”的线程状态
* The thread has completed execution.
* 线程已经完成了执行
*/
Terminated:此时线程已经执行完毕
可以发现,Java中的线程状态,是针对于JVM,或者说系统资源来定义的。对于JVM来说,只要有系统资源(CPU、IO资源等)在为线程服务,线程就是运行着的。处于非运行状态的线程,Java做了更详细的划分:
- 等待synchronized的锁:Blocked
- 不等待synchronized的锁,且没有时间限制:Waiting
- 不等待synchronized的锁,有时间限制:Timed_Waiting
线程状态的切换
读完上面的内容,相信大家对Java线程的各个状态都了解了,记住线程状态怎么切换自然也不难。
为了展示得更清晰,给大家画了一个图:
网上还有一些示意图,将调用Object.notify()/Objcet.notifyAll()之后的箭头直接指向了Blocked状态,这是因为Object.wait()方法必须在synchronized代码/方法中调用(wait方法的目的就是释放监视器的锁,这当然要先获得一个监视器的锁);这意味着在Object.notify()/Objcet.notifyAll()之后,线程仍要去尝试获取锁,线程有可能会变为Blocked状态。
我猜想应该先转为Runnable去尝试获取锁,如果直接获取到了自然不会Blocked了。但是也有不同的说法,还需要去查查别的资料。
至于这些线程相关的方法,留到下一篇再说。
参考资料: