并发编程专题(三)-线程的状态
1.线程状态
Java中,线程的状态使用一个枚举类型来描述的。这个枚举一共有6个值: NEW(新建)、RUNNABLE(运行)、BLOCKED(锁池)、TIMED_WAITING(定时等待)、WAITING(等待)、TERMINATED(终止、结束)。
但是大多数人的理解和上面的这六种还是有些差别,通常会加上阻塞状态,可运行状态,挂起状态。
在API中 java.lang.Thread.State
这个枚举中给出了六种线程状态,
这是Thread类描述线程状态的枚举类的源代码:
public enum State { /** * Thread state for a thread which has not yet started. */ NEW, /** * 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. */ RUNNABLE, /** * 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}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * * - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
* * A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. */ 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: *
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }
2.大多数人对线程状态以及状态转换的理解
线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
线程状态转换图:
2.1 新建状态
当用new操作符创建一个线程时, 例如new Thread(r)
,线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码。
2.2 就绪状态
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由
Java运行时系统的线程调度程序(thread scheduler)来调度的。
2.3 运行状态
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
2.4 阻塞状态
线程运行过程中,可能由于各种原因进入阻塞状态:
- 线程通过调用sleep方法进入睡眠状态;
2.线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3.线程试图得到一个锁,而该锁正被其他线程持有;
4.线程在等待某个触发条件;
2.5 死亡状态
有两个原因会导致线程死亡:
1.run方法正常退出而自然死亡,
- 一个未捕获的异常终止了run方法而使线程猝死。
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.3.对线程状态以及状态转换的另外理解
据官方源码,一个线程有六个状态,没有阻塞状态,没有可运行,没有挂起状态。这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析:
线程状态 | 导致状态发生修改的条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start()方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限 等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入TimedWaiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait。 |
Teminated(被 终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间 的转换问题。
3.1 TimedWaiting(计时等待)
当线程调用sleep()方法或当前线程中有其他线程调用了带时间参数的join()方法的时候进入了定时等待状态(TIMED_WAITING)。
代码示例:
/** * @author bruceliu * @create 2019-06-01 22:35 * @description TimedWaiting(计时等待) */public class Test1 { public static void main(String[] args) { Thread1 t1 = new Thread1(); Thread2 t2 = new Thread2(); t1.setThread2(t2); t1.start(); t2.start(); }}//Thread1负责打印两个线程的状态。class Thread1 extends Thread { private Thread2 t2; public void setThread2(Thread2 t2) { this.t2 = t2; } @Override public void run() { System.out.println("进入t1线程"); for (int i = 0; i < 5; i++) { try { System.out.println("t1 的状态: " + getState()); System.out.println("t2 的状态: " + t2.getState()); System.out.println(); //为了减少打印次数,所以t1每打印一次睡1秒 Thread.sleep(1000); } catch (InterruptedException e) { } } }}class Thread2 extends Thread { @Override public void run() { System.out.println("进入t2线程,马上进入睡眠"); try { //睡眠5秒钟。 sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2睡眠结束"); }}
通过案例可以发现,sleep方法的使用还是很简单的。我们需要记住下面几点:
- 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协作关系。
- 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠
- sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行。
Timed Waiting 线程状态图:
3.2 BLOCKED(锁阻塞)
Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
如果我们已经学完同步机制,那么这个状态是非常好理解的了。比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态,而这部分内容作为扩充知识点带领大家了解一下。
Blocked 线程状态图:
3.3 Waiting(无限等待)
Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。那么我们之前遇到过这种状态吗?答案是并没有,但并不妨碍我们进行一个简单深入的了解。我们通过一段代码来学习一下:
package com.bruceliu.demo7;/** * @author bruceliu * @create 2019-05-30 16:59 * @description */public class WaitingTest { public static Object obj = new Object(); public static void main(String[] args) { // 演示waiting new Thread(new Runnable() { @Override public void run() { while (true) { synchronized (obj) { try { System.out.println(Thread.currentThread().getName() + "=== 从waiting状态醒来,获取到锁对象,继续执行了"); obj.wait(); //无限等待 //obj.wait(5000); //计时等待, 5秒 时间到,自动醒来 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "=== 从waiting状 态醒来,获取到锁对象,继续执行了"); } } } }, "等待线程").start(); new Thread(new Runnable() { @Override public void run() { while (true) { //每隔3秒 唤醒一次 try { System.out.println(Thread.currentThread().getName() + "‐‐‐‐‐ 等待3秒钟"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj) { System.out.println(Thread.currentThread().getName() + "‐‐‐‐‐ 获取到锁对 象,调用notify方法,释放锁对象"); obj.notify(); } } } }, "唤醒线程").start(); }}
通过上述案例我们会发现,一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的Object.notify()方法 或 Object.notifyAll()方法。其实waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系,多个线程会争取锁,同时相互之间又存在协作关系。就好比在公司里你和你的同事们,你们可能存在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。
Waiting 线程状态图:
3.4 小结
到此为止我们已经对线程状态有了基本的认识,想要有更多的了解,详情可以见下图:
我们在翻阅API的时候会发现Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的,比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。这种带参的方法,其实是一种倒计时操作,相当于我们生活中的小闹钟,我们设定好时间,到时通知,可是如果提前得到(唤醒)通知,那么设定好时间在通知也就显得多此一举了,那么这种设计方案其实是一举两得。如果没有得到(唤醒)通知,那么线程就处于Timed Waiting状态,直到倒计时完毕自动醒来;如果在倒计时期间得到(唤醒)通知,那么线程从Timed Waiting状态立刻唤醒。