赞
踩
当线程被创建并启动以后, 它既不是一启动就进入执行状态, 也不是一直处于执行状态. 在线程的生命周期中, 有几种状态呢? 在 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 (可运行) 状态与非运行状态之间的转换问题.
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 秒!
通过上述案例我们可以发现, sleep 方法的使用还是很简单的. 我们需要记住下面几点:
注: sleep() 中指定的时间是线程不会运行的最短时间. 因此, sleep() 方法不会运行的最短时间. 因此, sleep() 不能保证该线程睡眠到期后就开始立刻执行.
Blocked 状态在 API 中的介绍为: 一个正在阻塞等待一个监视器锁 (锁对象) 的线程处于这一状态.
我们已经学完同步机制, 那么这个状态是非常好理解的了. 你如, 线程 A 与线程 B 代码中使用同一锁, 如果线程 A 获取到锁, 线程 A 进入到 Runnable 状态, 那么线程 B 就进入到 Blocked 锁阻塞状态.
这是由 Runnable 状态进入 Blocked 状态. 除此 Waiting 以及 TIme 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 状态, 释放锁对象
通过上述案例我们会发信, 一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的 Object.notify() 方法 或 Object.notifyAll() 方法.
其实 waiting 状态并不是一个线程的操作, 它体现的是多个线程之间的通信. 可以理解为多个线程之间的协作关系, 多个线程会争取锁, 同时相互之间又存在协作关系. 就好比在公司里你和你同事们, 你们可能存在晋升时的竞争, 但更多时候你们更多是一起合作以完成某些任务.
当多个线程协作时, 比如 A, B 线程. 如果 A 在线程在 Runnable (可运行) 状态中调用了 wait() 方法那么 A 线程就进入了 Waiting (无限等待) 状态, 同时失去了同步锁. 假如这个时候 B 线程获取到了同步锁, 在运行状态中调用了 notify() 方法, 那么就会将无限等待的 A 线程唤醒. 注意是唤醒, 注意是唤醒, 如果获取到锁对象, 那么 A 线程唤醒后进入 Runnable (可运行) 状态. 如果没有获取锁对象, 那么就进入到 Blocked (锁阻塞状态).
到此为止我们已经对线程状态有了基本的认识. 想要有更多的了解. 详情可以见下图:
我们在翻阅 API 的时候会发现 Timed Waiting (计时等待) 与 Waiting (无限等待) 状态练习还是很紧密的. 比如 Waiting (无限等待) 状态中 wait 方法是空参的, 而 timed waiting (计时等待) 中 wait 方法是带参的. 这种带参的方法, 其实是一种倒计时操作, 相当于我们生活中的小闹钟. 我们设定好时间, 到时候通知, 可是如果提前得到 (唤醒) 通知, 那么设定好时间再通知就显得多此一举了. 那么这种设计方案其实是一举两的. 如果没有得到 (唤醒) 通知, 那么线程就处于 Timed Waiting 状态, 直到倒计时完毕自动醒来. 如果在倒计时期间得到 (唤醒) 通知, 那么线程从 Timed Waiting 状态立刻唤醒.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。