Java基础 第四节 第三课

举报
我是小白呀iamarookie 发表于 2021/09/11 02:05:43 2021/09/11
【摘要】 线程状态 概述Tiemed Waiting (计时等待)案例Timed Waiting 线程状态图 Blocked (锁阻塞)Blocked 线程状态图 Waiting (无限等待)Wai...

概述

当线程被创建并启动以后, 它既不是一启动就进入执行状态, 也不是一直处于执行状态. 在线程的生命周期中, 有几种状态呢? 在 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 方法的使用还是很简单的. 我们需要记住下面几点:

  1. 进入 TIMED_WAITING 状态的一种常见形式是调用的 sleep 方法, 单独的线程也可以调用, 不一定非要有协作关系
  2. 为了让其他线程有机会执行, 可以将 Thread.sleep() 的调用放线程 run() 之内. 这样才能保证该线程执行过程中会睡眠
  3. 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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。