线程状态
# 线程状态
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
# 线程的六种状态
在线程的生命周期中,有几种状态呢?在 API 中 java.lang.Thread.State
这个枚举(Thread 的内部类)中给出了六种线程状态:
线程状态 | 导致状态发生的条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动,还没调用 start 方法 |
Runnable(可运行) | 线程可以在 Java 虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入 Blocked 状态;当该线程持有锁时,该线程将变成 Runnable 状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入 Waiting 状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用 Object.notify 或者 Object.notifyAll 方法才能够唤醒 |
Timed Waiting(计时等待) | 同 waiting 状态,有几个方法有超时参数,调用它们将进入 Timed Waiting 状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有 Thread.sleep 、Object.wait 。 |
Teminated(被终止) | 因为 run 方法正常退出而死亡,或者因为没有捕获的异常终止了 run 方法而死亡 |
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析。
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢?新建与被终止还是很容易理解的,我们就研究一下线程从 Runnable(可运行)状态与非运行状态之间的转换问题。
注意,阻塞状态是由于锁,被动放弃了 CPU 的控制权。而 Waiting 和 Timed Waiting 则是主动放弃 CPU 控制权。
# Timed Waiting(计时等待)
Timed Waiting 在 API 中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢?
在我们写卖票的案例中,为了减少线程执行太快,现象不明显等问题,我们在 run 方法中添加了 sleep 语句,这样就强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。
其实当我们调用了 sleep 方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的 Timed Waiting(计时等待)
那么我们通过一个案例加深对该状态的一个理解:实现一个计数器,计数到 100,在每个数字之间暂停 1 秒,每隔 10 个数字输出一个字符串。示例代码如下。
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
if ((i) % 10 == 0) {
System.out.println("‐‐‐‐‐‐‐" + i);
}
System.out.print(i);
try {
Thread.sleep(1000);
System.out.print(" 线程睡眠1秒!\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MyThread().start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
通过案例可以发现,sleep 方法的使用还是很简单的。我们需要记住下面几点:
- 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协作关系。
- 为了让其他线程有机会执行,可以将
Thread.sleep()
的调用放线程run()
之内。这样才能保证该线程执行过程中会睡眠 - sleep 与锁无关,线程睡眠到期自动苏醒,并返回到 Runnable(可运行)状态。
小提示:sleep()
中指定的时间是线程不会运行的最短时间。因此,sleep()
方法不能保证该线程睡眠到期后就开始立刻执行。
Timed Waiting 线程状态图:
# BLOCKED(锁阻塞)
Blocked 状态在 API 中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
我们已经学完同步机制,那么这个状态是非常好理解的了。比如,线程 A 与线程 B 代码中使用同一锁,如果线程 A 获取到锁,线程 A 进入到 Runnable 状态,那么线程 B 就进入到 Blocked 锁阻塞状态。
这是由 Runnable 状态进入 Blocked 状态。除此 Waiting 以及 Time Waiting 状态也会在某种情况下进入阻塞状态,而这部分内容作为扩充知识点带领大家了解一下。
Blocked 线程状态图:
# Waiting(无限等待)
我们重点来看看这个状态。线程之间通信时,常常会用到这两个方法。
Wating 状态在 API 中介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态,这个状态我们之前并没有学过。
Waiting 线程状态图:
举个生活中的例子:
- 顾客到包子铺,说明要买什么包子
- 说完后,顾客就开始等待老板做包子(调用 wait 方法)
- 老板开始做包子,做好后告诉顾客(调用 notify 方法)
我们通过代码来模拟:
- 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用 wait 方法,放弃 CPU 的执行,进入到 WAITING 状态(无限等待)
- 创建一个老板线程(生产者):花了 5 秒做包子,做好包子之后,调用 notify 方法,唤醒顾客吃包子
注意事项:
- 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
- 同步使用的锁对象必须保证唯一
- 只有锁对象才能调用 wait 和 notify 方法
示例代码:
package chapter200Thread;
public class Demo09WaitAndNotify {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
// 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("告知老板要的包子的种类和数量");
// 调用wait()方法,放弃cpu的执行,进入到等待状态
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后执行的代码
System.out.println("包子已经做好,开吃!");
}
}
}.start();
// 创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
// 模拟5秒做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println("包子已经做好,告知顾客可以吃包子了");
// 调用notify()方法,唤醒顾客线程
obj.notify();
}
}
}.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
wait 方法还可以传参,参数是毫秒值。如果在毫秒值结束后还没被 notify 唤醒,就会自动醒来。
示例:
package chapter200Thread;
public class Demo10WaitTime {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
// 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
synchronized (obj){
System.out.println("告知老板要的包子的种类和数量");
// 调用wait()方法,放弃cpu的执行,进入到等待状态
try {
obj.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后执行的代码
System.out.println("包子已经做好,开吃!");
}
}
}.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
至此,线程进入 Time Waiting 有两种方式:
- 使用
Thread.sleep(lone m)
方法 - 使用
wait (lone m)
方法
唤醒也有两种方式:
notify()
唤醒等待中的线程(如果有多个,则通过调度唤醒其中一个)notifyAll()
唤醒全部等待中的线程
# 补充
到此为止我们已经对线程状态有了基本的认识,想要有更多的了解,详情可以见下图: