当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中, 有几种状态呢?下面介绍一下常见的线程状态及等待唤醒机制
1. 线程状态概述
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题
先列出各个线程状态,下面会详细讲解
线程状态
导致状态发生改变的条件
NEW(新建)
线程刚被创建,但是并未启动,还没有调用start方法
Runnable(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器
Blocked(锁阻塞)
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态
Timed Waiting(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态,这一状态将一直保持到超时期满或者接收到唤醒通知,带有超时参数的常用方法有Thread.sleep、锁对象.wait()
Waiting(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
Teminated(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
1.1 BLOCKED锁阻塞
Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。 我们已经学完同步机制,那么这个状态是非常好理解的了。
比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。 这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态,而这部分内容作为扩充知识点带领大家了解一下
BLOCKED 线程状态图
1.2 Timed Waiting 计时等待
Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一(唤醒)动作的线程处于这一状态。单独的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢? 在我们写卖票的案例中,为了减少线程执行太快,现象不明显等问题,我们在run方法中添加了sleep语句,这样就 强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。 其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待)
举例说明:
-
实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串
/**
- @Description
- @auther 宁宁小可爱
- @create 2020-01-10 13:02
*/
public class ThreadDemo {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (i % 10 == 0){
System.out.println(“-----------------” + i);
}
System.out.println(i);
try {
Thread.sleep(1000);
System.out.println(“线程睡眠一秒! \n”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
-
执行结果
关于Sleep的几点说明:
- 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要多线程
- 不仅main线程可以调用sleep,其他子线程也可以调用Thread.sleep()的调用放线程run()之内,这样才能保证 该线程执行过程中会睡眠
- 获取锁对象之前和之后,线程均可以sleep
- 如果线程获取锁之后调用sleep,那么该线程不会释放锁对象,线程睡眠到期自动苏醒,并返回到 Runnable(可运行)状态
- sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始立刻执行,还要看取到锁没有,没取到进入锁阻塞状态
Time Wating 线程状态图
1.3 Waiting(无限等待)
Wating状态在API中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。 那么我们之前遇到过这种状态吗?答案是并没有,但并不妨碍我们进行一个简单深入的了解。 那么如何让一个线程无限等待呢?
进入无限等待状态的前提
- 该线程必须获取了锁对象
- 调用线程的wait方法,那么锁对象的持有者就会无限等待了
- 同时该线程会释放锁,别的线程就有机会获取锁对象了
- 那么该线程得到锁对象后,就可以调用锁对象的notify/notifyAll方法,唤醒刚刚进入无限等待的线程对象
- 需要注意的是:被唤醒的线程,并不会进入Runnable,因为没有锁对象,只有其他线程释放了锁对象并且被唤醒的线程获取到了锁对象该线程才会真正的进去Runnable状态
无限等待真的是无限的吗?作为初学者,一定不能被表面的单词所迷惑!!! 其实这里的无限等待是指线程不会自动"醒来"(不像sleep那样,时间到了就会自动"醒来"), 但是别的线程可以"唤醒"它 上面我们提到,处于Waiting状态的线程会释放锁对象,那么其他线程就有机会获取锁对象
举例说明
/**
* @Description
* @auther 宁宁小可爱
* @create 2020-01-10 15:51
*/
public class WaitingDemo {
// 最先执行 static修饰的变量
public static Object obj = new Object();
public static void main(String[] args) {
// 创建匿名内部类 演示waiting
new Thread(new Runnable(){
// 重写run方法
@Override
public void run(){
while (true){
synchronized (obj){
try {
System.out.println(Thread.currentThread().getName() + "=== 获取到锁对象,调用wait方法,进入waiting状态,释放锁对象");
// 进入无限等待状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "=== 从waiting状态醒来 ,获取到锁对象 ,继续执行了");
}
}
}
},"等待线程").start();
new Thread(new Runnable(){
// 重写run方法
@Override
public void run(){
while (true){
try {
System.out.println(Thread.currentThread().getName() + "等待三秒钟");
// 等待
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println(Thread.currentThread().getName() + "获取到锁对象,调用notify方法,释放锁对象");
obj.notify();
}
}
}
},"唤醒线程").start();
}
}
- 执行结果,这里是一直循环的,我执行完一遍关掉了
其实waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系, 多个线程会争取锁,同时相互之间又存在协作关系。就好比在公司里你和你的同事们,你们可能存在晋升时的竞 争,但更多时候你们更多是一起合作以完成某些任务。 当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入 了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了 notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入 Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)
Waiting 线程状态图
1.4 全部状态流程图
我们在翻阅API的时候会发现Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的, 比如Waiting(无限等待) 状态,是调用 锁对象.wait()方法,而timed waiting(计时等待) 状态,是调用锁对象.wait(毫秒值)方法。这种带参的方法,其实是一种倒计时操作,相当于我们生活中的小闹钟. 这种带有参数的wait(毫秒值)方法,相当于双重保险,如果提前有人notify,那么可以唤醒,如果没有人notify那么 时间到了也可以自动醒来