Java基础 第四节 第三课
概述
当线程被创建并启动以后, 它既不是一启动就进入执行状态, 也不是一直处于执行状态. 在线程的生命周期中, 有几种状态呢? 在 API 中java.lang.Thread.State
这个枚举中给出了六种线程状态:
线程状态 | 导致状态发生条件 |
---|---|
NEW (新建) | 线程刚被创建, 但是还并未启动. 还没调用 start 方法 |
Runnable (可运行) | 线程可以在 java 虚拟机中运行的状态: 可能在运行自己的代码, 可能没有, 取决于操作系统处理器 |
Blocked (锁阻塞) | 当一个线程试图获取一个对象锁, 而该对象锁被其他的线程持有, 则该线程进入 Blocked 状态. 当线程持有锁时, 该线程将变成 Runnable 状态 |
Waiting (无限等待) | 一个线程在等待另一个线程执行一个 (唤醒) 动作时, 该线程进入 Waiting 状态. 进入这个状态后是不能自动唤醒的, 必须等待另一个线程调用 notify 或者 notifyAll 方法才能够唤醒 |
Timed Waiting (计等待) | 同 waiting 状态, 有几个方法有超时参数, 调用他们将进入 Timed Waiting 状态. 这一状态将一直保持到超时期慢或者接收到唤醒通知. 带有超时参数的常用方法有 Thread.sleep, Object.wait |
Teminated (被终止) | 因为 run 方法正常退出而终止, 或者因为没有捕获的异常终止了 run 方法而终止 |
我们不需要去研究这几种状态的实现原理. 我们只需知道在做线程操作中存在这样的状态. 那么我们怎么去理解这几个状态呢? 新建与被终止还是很容易理解的. 我们就研究一下线程从 Runnable (可运行) 状态与非运行状态之间的转换问题.
Tiemed Waiting (计时等待)
Timed Waiting 在 API 中的描述为: 一个正在限时等待另一个线程执行一个 (唤醒) 动作的线程处于这一状态. 我们其实在之前的操作中已经解除过这个状态了.
在我们写卖票的案例中, 为了减少线程执行太快, 现象不明显等问题. 我们在 run 方法中添加了 sleep 语句, 这样就强制当前正在执行的线程休眠 (暂停执行), 以 “减慢线程”.
案例
其实当我们调用了 sleep 方法之后, 当前执行的线程就进入到 “休眠状态”, 其实就是所谓的 Timed Waiting (计时等待), 那么我们通过一个案例加深对该状态的一个理解.
实现一个计数器, 计数到 10, 在每个数字之间暂停 1 秒. 代码:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
System.out.println("线程睡眠 1 秒! \n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MyThread().start();
}
}
输出结果:
0
线程睡眠 1 秒!
1
线程睡眠 1 秒!
2
线程睡眠 1 秒!
3
线程睡眠 1 秒!
4
线程睡眠 1 秒!
5
线程睡眠 1 秒!
6
线程睡眠 1 秒!
7
线程睡眠 1 秒!
8
线程睡眠 1 秒!
9
线程睡眠 1 秒!
10
线程睡眠 1 秒!
11
线程睡眠 1 秒!
12
线程睡眠 1 秒!
13
线程睡眠 1 秒!
14
线程睡眠 1 秒!
15
线程睡眠 1 秒!
16
线程睡眠 1 秒!
17
线程睡眠 1 秒!
18
线程睡眠 1 秒!
19
线程睡眠 1 秒!
- 1
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
通过上述案例我们可以发现, sleep 方法的使用还是很简单的. 我们需要记住下面几点:
- 进入 TIMED_WAITING 状态的一种常见形式是调用的 sleep 方法, 单独的线程也可以调用, 不一定非要有协作关系
- 为了让其他线程有机会执行, 可以将 Thread.sleep() 的调用放线程 run() 之内. 这样才能保证该线程执行过程中会睡眠
- sleep 与锁无关, 线程睡眠到期自动苏醒, 并返回到 Runnable (可运行) 状态
注: sleep() 中指定的时间是线程不会运行的最短时间. 因此, sleep() 方法不会运行的最短时间. 因此, sleep() 不能保证该线程睡眠到期后就开始立刻执行.
Timed Waiting 线程状态图
Blocked (锁阻塞)
Blocked 状态在 API 中的介绍为: 一个正在阻塞等待一个监视器锁 (锁对象) 的线程处于这一状态.
我们已经学完同步机制, 那么这个状态是非常好理解的了. 你如, 线程 A 与线程 B 代码中使用同一锁, 如果线程 A 获取到锁, 线程 A 进入到 Runnable 状态, 那么线程 B 就进入到 Blocked 锁阻塞状态.
这是由 Runnable 状态进入 Blocked 状态. 除此 Waiting 以及 TIme Waiting 状态也会在某种情况下进入阻塞状态, 而这部分内容作为扩充知识点带大家了解一下.
Blocked 线程状态图
Waiting (无限等待)
Waiting 状态在 API 中介绍为: 一个正在无限期等待另一个线程执行一个特别的 (唤醒) 动作的线程处于这一状态. 我们目前还没有遇到过这种状态, 但不妨碍我们进行一个简单深入的了解. 我们通过一端代码学习一下:
public class Test52 {
public static Object object = new Object();
public static void main(String[] args) {
// 演示waiting
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (object) {
try {
String name = Thread.currentThread().getName();
System.out.println(name + "=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "=== 从 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 (object) {
String name = Thread.currentThread().getName();
System.out.println(name + "--- 获取到锁对象, 调用 notify 方法, 释放锁对象");
object.notify();
}
}
}
}, "唤醒线程").start();
}
}
输出结果:
唤醒线程‐‐‐‐‐ 等待3秒钟
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
唤醒线程--- 获取到锁对象, 调用 notify 方法, 释放锁对象
唤醒线程‐‐‐‐‐ 等待3秒钟
等待线程=== 从 waiting 状态醒来, 获取到锁对象, 继续执行了
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
唤醒线程--- 获取到锁对象, 调用 notify 方法, 释放锁对象
等待线程=== 从 waiting 状态醒来, 获取到锁对象, 继续执行了
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
唤醒线程‐‐‐‐‐ 等待3秒钟
唤醒线程--- 获取到锁对象, 调用 notify 方法, 释放锁对象
等待线程=== 从 waiting 状态醒来, 获取到锁对象, 继续执行了
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
唤醒线程‐‐‐‐‐ 等待3秒钟
唤醒线程--- 获取到锁对象, 调用 notify 方法, 释放锁对象
等待线程=== 从 waiting 状态醒来, 获取到锁对象, 继续执行了
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
唤醒线程‐‐‐‐‐ 等待3秒钟
唤醒线程--- 获取到锁对象, 调用 notify 方法, 释放锁对象
等待线程=== 从 waiting 状态醒来, 获取到锁对象, 继续执行了
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
唤醒线程‐‐‐‐‐ 等待3秒钟
唤醒线程--- 获取到锁对象, 调用 notify 方法, 释放锁对象
等待线程=== 从 waiting 状态醒来, 获取到锁对象, 继续执行了
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
唤醒线程‐‐‐‐‐ 等待3秒钟
唤醒线程--- 获取到锁对象, 调用 notify 方法, 释放锁对象
等待线程=== 从 waiting 状态醒来, 获取到锁对象, 继续执行了
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
唤醒线程‐‐‐‐‐ 等待3秒钟
唤醒线程--- 获取到锁对象, 调用 notify 方法, 释放锁对象
等待线程=== 从 waiting 状态醒来, 获取到锁对象, 继续执行了
等待线程=== 获取到锁对象时, 调用 wait 方法, 进入 waiting 状态, 释放锁对象
- 1
- 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
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
通过上述案例我们会发信, 一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的 Object.notify() 方法 或 Object.notifyAll() 方法.
其实 waiting 状态并不是一个线程的操作, 它体现的是多个线程之间的通信. 可以理解为多个线程之间的协作关系, 多个线程会争取锁, 同时相互之间又存在协作关系. 就好比在公司里你和你同事们, 你们可能存在晋升时的竞争, 但更多时候你们更多是一起合作以完成某些任务.
当多个线程协作时, 比如 A, B 线程. 如果 A 在线程在 Runnable (可运行) 状态中调用了 wait() 方法那么 A 线程就进入了 Waiting (无限等待) 状态, 同时失去了同步锁. 假如这个时候 B 线程获取到了同步锁, 在运行状态中调用了 notify() 方法, 那么就会将无限等待的 A 线程唤醒. 注意是唤醒, 注意是唤醒, 如果获取到锁对象, 那么 A 线程唤醒后进入 Runnable (可运行) 状态. 如果没有获取锁对象, 那么就进入到 Blocked (锁阻塞状态).
Waiting 线程状态图
补充知识点
到此为止我们已经对线程状态有了基本的认识. 想要有更多的了解. 详情可以见下图:
我们在翻阅 API 的时候会发现 Timed Waiting (计时等待) 与 Waiting (无限等待) 状态练习还是很紧密的. 比如 Waiting (无限等待) 状态中 wait 方法是空参的, 而 timed waiting (计时等待) 中 wait 方法是带参的. 这种带参的方法, 其实是一种倒计时操作, 相当于我们生活中的小闹钟. 我们设定好时间, 到时候通知, 可是如果提前得到 (唤醒) 通知, 那么设定好时间再通知就显得多此一举了. 那么这种设计方案其实是一举两的. 如果没有得到 (唤醒) 通知, 那么线程就处于 Timed Waiting 状态, 直到倒计时完毕自动醒来. 如果在倒计时期间得到 (唤醒) 通知, 那么线程从 Timed Waiting 状态立刻唤醒.
文章来源: iamarookie.blog.csdn.net,作者:我是小白呀,版权归原作者所有,如需转载,请联系作者。
原文链接:iamarookie.blog.csdn.net/article/details/111464100
- 点赞
- 收藏
- 关注作者
评论(0)