Java线程的生命周期
New(新创建)
Runnable(可运行)
Blocked(被阻塞)
Waiting(等待)
Timed Waiting(计时等待)
Terminated(被终止)
在我们程序编码中如果想要确定线程当前的状态,可以通过<code>getState()</code>方法来获取,同时我们需要注意任何线程在任何时刻都只能是处于一种状态。
java api java.lang.Thread.State 这个枚举中给出了六种线程状态,分别是:
<col>
线程状态
导致状态发生条件
NEW(新建)
线程刚被创建,但是并未启动。 还没调用start方法 。
Runnable(可运行)
Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
ready: 线程**等待被线程调度器选中,得到时间片(获取cpu的执行权 ) **就能在cpu上面执行
running: 就绪状态的线程在获得CPU时间片后变为运行中状态(running)
是否获取到时间片就是这两者状态的区别
阻塞(BLOCKED):
受阻塞并等待某个监视器锁的线程处于这种状态
等待(WAITING):
进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)
超时等待(TIMED_WAITING):
该状态不同于WAITING,它可以在指定的时间后自行返回。
. 终止(TERMINATED):
表示该线程已经执行完毕。
一、线程的状态图

二、状态详细说明
1.初始状态(NEW)
<code>New</code>表示线程被创建但尚未启动的状态:当我们用 <code>new Thread()</code>新建一个线程时,如果线程没有开始运行 <code>start()</code> 方法,那么线程也就没有开始执行 <code>run()</code>方法里面的代码,那么此时它的状态就是 <code>New</code>。而一旦线程调用了 <code>start()</code>,它的状态就会从 <code>New</code>变成 <code>Runnable</code>,进入到图中绿色的方框
2.可运行状态(RUNNABLE)
<code>Runnable</code> 状态的线程有可能正在执行,
也有可能没有执行,正在等待被分配 CPU 资源。
2.1就绪状态(RUNNABLE之READY)
调用线程的start()方法,此线程进入就绪状态。
就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,这些线程也将进入就绪状态。
当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
锁池里的线程拿到对象锁后,进入就绪状态。
2.2. 运行中状态(RUNNABLE之RUNNING)
就绪状态和运行状态可以相互转换,比如 正在执行的线程时间片没了,此时调度器把cpu的时间片给了其他线程。
正在执行的线程也可以自动调用yield方法主动放弃时间片,将cpu的执行权给别的线程
3.阻塞状态(BLOCKED)
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(未获取monitor锁)时的状态。
我们可以通过下面的图示看到,从 <code>Runnable</code> 状态进入到 <code>Blocked</code> 状态只有一种途径,那么就是当进入到 <code>synchronized</code> 代码块中时未能获得相应的 <code>monitor</code> 锁(关于 <code>monitor</code> 锁我们在之后专门来介绍,这里我们知道 <code>synchronized</code> 的实现都是基于 <code>monitor</code> 锁的)
在右侧我们可以看到,有连接线从 <code>Blocked</code> 状态指向了 <code>Runnable</code> ,也只有一种情况,那么就是当线程获得 <code>monitor</code> 锁,此时线程就会进入 <code>Runnable</code> 状体中参与 <code>CPU</code> 资源的抢夺
4.等待(WAITING)
对于 <code>Waiting</code> 状态的进入有三种情况,如下图中所示,分别为:
当线程中调用了没有设置 <code>Timeout</code> 参数的 <code>Object.wait()</code> 方法
当线程调用了没有设置 <code>Timeout</code> 参数的 <code>Thread.join()</code> 方法
当线程调用了 <code>LockSupport.park()</code> 方法
关于 <code>LockSupport.park()</code> 方法,这里说一下,我们通过上面知道 <code>Blocked</code> 是针对 <code>synchronized monitor</code> 锁的, 但是在 <code>Java</code> 中实际是有很多其他锁的: 比如 <code>ReentrantLock</code> 等,在这些锁中,如果线程没有获取到锁则会直接进入 <code>Waiting</code> 状态,其实这种本质上它就是执行了 <code>LockSupport.park()</code> 方法进入了<code>Waiting</code> 状态
<code>**Blocked **</code>与 **<code>**Waiting**</code> 的区别**
<code>Blocked</code> 是在等待其他线程释放 <code>monitor</code> 锁 ,其他条件都满足了吧
<code>Waiting</code> 则是在等待某个条件,比如 <code>join</code> 的线程执行完毕,或者是 <code>notify()/notifyAll()</code>。
5.超时等待(TIMED_WAITING)
最后我们来说说这个 <code>Timed Waiting</code> 状态,它与 <code>Waiting</code> 状态非常相似,其中的区别只在于是否有时间的限制,在 <code>Timed Waiting</code> 状态时会等待超时,之后由系统唤醒,或者也可以提前被通知唤醒如 <code>notify</code>
处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。通过上述图我们可以看到在以下情况会让线程进入 <code>Timed Waiting</code>状态。
线程执行了设置了时间参数的<code>Thread.sleep(long millis)</code> 方法;
线程执行了设置了时间参数的 <code>Object.wait(long timeout)</code> 方法;
线程执行了设置了时间参数的 <code>Thread.join(long millis)</code> 方法;
线程执行了设置了时间参数的 <code>LockSupport.parkNanos(long nanos)</code> 方法和 <code>LockSupport.parkUntil(long deadline)</code> 方法。
通过这个我们可以进一步看到它与 waiting 状态的相同
6.终止状态(TERMINATED)
run() 方法执行完毕,线程正常退出。
出现一个没有捕获的异常,终止了 run() 方法,最终导致意外终止。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常
二:线程状态间转换
上面我们讲了各自状态的特点和运行状态进入相应状态的情况 ,那么接下来我们将来分析各自状态之间的转换,其实主要就是 <code>Blocked</code>、<code>waiting</code>、<code>Timed Waiting</code> 三种状态的转换 ,以及他们是如何进入下一状态最终进入 <code>Runnable</code>
想要从 <code>Blocked</code> 状态进入<code>Runnable</code> 状态,我们上面说过必须要线程获得 <code>monitor</code> 锁,但是如果想进入其他状态那么就相对比较特殊,因为它是没有超时机制的,也就是不会主动进入。
如下图中紫色加粗表示线路:
只有当执行了<code>LockSupport.unpark()</code>,或者<code>join</code> 的线程运行结束,或者被中断时才可以进入 <code>Runnable</code> 状态。
如下图标注
如果通过其他线程调用 <code>notify()</code> 或 <code>notifyAll()</code>来唤醒它,则它会直接进入 <code>Blocked</code> 状态,这里大家可能会有疑问,不是应该直接进入 <code>Runnable</code> 吗?这里需要注意一点 ,因为唤醒 <code>Waiting</code> 线程的线程如果调用 <code>notify()</code> 或 <code>notifyAll()</code>,要求必须首先持有该 <code>monitor</code> 锁,这也就是我们说的 <code>wait()</code>、<code>notify</code> 必须在 <code>synchronized</code> 代码块中。
所以处于 <code>Waiting</code> 状态的线程被唤醒时拿不到该锁,就会进入 <code>Blocked</code> 状态,直到执行了 <code>notify()/notifyAll()</code> 的唤醒它的线程执行完毕并释放 <code>monitor</code> 锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从 <code>Blocked</code> 状态回到 <code>Runnable</code> 状态。
同样在 <code>Timed Waiting</code> 中执行 <code>notify()</code> 和 <code>notifyAll()</code>也是一样的道理,它们会先进入 <code>Blocked</code> 状态,然后抢夺锁成功后,再回到 <code>Runnable</code> 状态。
但是对于 Timed Waiting 而言,它存在超时机制,也就是说如果超时时间到了那么就会系统自动直接拿到锁,或者当 <code>join</code> 的线程执行结束/调用了<code>LockSupport.unpark()</code>/被中断等情况都会直接进入 <code>Runnable</code> 状态,而不会经历 <code>Blocked</code> 状态
三: 从mointorObject 的角度上理解阻塞与等待的区别
monitor可以把它理解为 一个同步工具,也可以描述为 一种同步机制,它通常被 描述为一个对象。
与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。也就是通常说Synchronized的对象锁。
在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的):
ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象 ),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时(即访问同一临界区时)
首先这些线程会进入 _EntryList 集合(处于阻塞状态的线程会放到该列表中),当线程A获取到对象的monitor后,Monitor是依赖于底层操作系统的mutex lock来实现互斥的,线程获取mutex成功,则会持有该mutex,这时其它线程就无法再获取到该mutex。线程A进入 _Owner区域并把monitor中的owner变量设置为当前线程A,同时monitor中的计数器count加1;
若线程A调用 wait() 方法,释放当前它锁持有的mutex, owner变量恢复为null,count自减1,并且该线程会进入到WaitSet集合(等待集合)中,等待下一次被其他线程调用notify/notifyAll唤醒。;
若线程A执行完毕,也将释放monitor(mutex锁)并复位count的值,以便其他线程获取monitor(mutex锁)进入monitor;
总结一下:
那些处于EntrySet和WaitSet中的线程均处于阻塞状态(OS级别我们叫做阻塞,Java不叫作阻塞 。。javascript:void(0) 这边有详解讲解os线程状态跟Java线程状态的区别),
阻塞操作是由操作系统来完成的,线程被阻塞后便会进入到内核调度状态。线程的阻塞和唤醒需要CPU从用户态转为核心态(也叫做内核陷入),频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。(在linux下是通过pthread_mutex_lock函数实现的)
同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。
所谓自旋锁,就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。怎么等待呢?执行一段无意义的循环即可(自旋)
即:当发生对Monitor的争用时,若owner线程能够在很短的时间内释放掉锁,则那些正在争用的线程就可以稍微等待一下(既所谓的自旋),在Owner线程释放锁之后,争用线程可能会立刻获取到锁,从而避免了系统阻塞。
不过,当Owner运行的时间超过了临界值后,争用线程自旋一段时间后依然无法获取到锁,这时争用线程则会停止自旋而进入到阻塞状态。
所以总体的思想是:先自旋,不成功再进行阻塞,尽量降低阻塞的可能性,这对那些执行时间很短的代码来说有极大的性能提升。显然,自旋在多处理器(多核心)上才有意义 。
总结
最后我们说一下再看线程转换的过程中一定要注意两点:
线程的状态是按照箭头方向来走的,比如线程从 <code>New</code>状态是不可以直接进入 <code>Blocked</code> 状态的,它需要先经历 <code>Runnable</code> 状态。
线程生命周期不可逆:一旦进入 <code>Runnable</code> 状态就不能回到 <code>New</code> 状态;一旦被终止就不可能再有任何状态的变化。
所以一个线程只能有一次 <code>New</code>和 <code>Terminated</code>状态,只有处于中间状态才可以相互转换。也就是这两个状态不会参与相互转化