线程状态
线程状态概述
- 当创建线程启动后,并不会直接进入可执行状态,也不会一直处于执行状态。在线程的生命周期中共有6中线程状态(在API中 java.lang.Thread.State)
线程状态 | 状态发生条件· |
---|---|
new(新建) | 线程刚被创建,但是还没有启动(没调start方法) |
Runnable(就绪) | 线程可以在java虚拟机中运行的状态 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入blocked状态,当该线程持有锁时,该线程将变成Runnable状态 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个唤醒动作时,该线程进入Waiting状态。进入这个状态后不能自动唤醒,必须等待另一个线程调用notifyAll和notify方法才能唤醒 |
Timed Waiting(计时等待) | 同Waiting状态,有几个方法有超时参数,调用他们将进入 Timed Waiting状态。这一状态将一直保持到超时期限满或者接受到唤醒通知,带有超时参数常用方法有Thread.sleep,object.wait |
Teminated(被终止) | 因run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
Timed Waiting(计时等待)状态
案例
实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串:
代码
public class Mythread extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
//没隔10数字输出一个
if ((i)%10==0) {
System.out.println("----"+i);
}
System.out.print(i);
try {
//线程睡眠1秒
Thread.sleep(1000);
System.out.print("线程睡眠1秒!\n");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Mythread().start();
}
}
sleep方法使用非常就简单,要记住一下几点:
- 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协 作关系。
- 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程 中会睡眠
- sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
Blocked(锁阻塞) 状态
介绍:
线程A与线程B代码中使用同一锁,如果线程A获 取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
Waiting(无限等待)状态
代码举例:
public class WaitingTest {
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 {
//获取线程对象名称
System.out.println(Thread.currentThread().getName() +
"=== 获取 到锁对象,调用wait方法,进入waiting状态,释放锁对象");
object.wait();//没有参数就是无限等待
//obj.wait(5000); //计时等待, 5秒 时间到,自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"=== 从 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){
System.out.println( Thread.currentThread().getName()
+"----- 获取到 锁对象,调用notify方法,释放锁对象");
object.notify();
}
//}
}
},"唤醒线程").start();
}
}
结果:
等待线程=== 获取 到锁对象,调用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状态立刻唤醒。
等待唤醒机制
线程之间通讯
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成肉的,线程B用来吃肉的,肉可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
为什么要处理线程间通信:
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源:
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
什么是等待唤醒机制?
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
- wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
- notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
- notifyAll:则释放所通知对象的 wait set 上的全部线程。
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用wait 方法之后的地方恢复执行。
总结:
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态
调用wait和notify方法需要注意的细节
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
- wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
生产者与消费者
等待唤醒机制其实就是经典的“生产者与消费者
就拿生产肉消费肉来说等待唤醒机制如何有效利用资源:
猪肉铺线程生产肉,吃货线程消费肉。当肉没有时(肉状态为false),吃货线程等待,肉铺线程生产肉(即 肉状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有肉了,那么肉铺线程进入等待状态。接下 来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃肉动作,肉吃完(肉状态为 false),并通知肉铺线程(解除肉铺的等待状态),吃货线程进入等待。肉铺线程能否进一步执行则取决于锁的获取 情况。
代码
肉资源类
public class Rou {
String zhurou;
String niurou;
//肉资源 是否存在 肉资源状态
boolean flag=false;
}
吃货类
public class Chihuo extends Thread {
private Rou rou;
public Chihuo(String name, Rou rou) {
super(name);
this.rou = rou;
}
@Override
public void run(){
while (true){
synchronized (rou){
//没肉
if (rou.flag==false){
try {
rou.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货再吃"+rou.niurou+rou.zhurou);
rou.flag=false;
rou.notify();
}
}
}
}
肉铺
public class RouPu extends Thread{
private Rou rou;
public RouPu(String name, Rou rou) {
super(name);
this.rou = rou;
}
public void run(){
int count=0;
//杀猪取肉
while (true){
//同步
synchronized (rou){
//有肉
if (rou.flag==true){
try {
rou.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没肉,杀猪取肉
System.out.println("肉铺开始杀猪");
if (count%2==0){
//猪肉 牛肉
rou.zhurou="牛肉";
rou.niurou="爆炒";
}else {
rou.niurou="炸";
rou.zhurou="耗子肉";
}
count++;
rou.flag=true;
System.out.println("肉造好了:"+rou.niurou+rou.zhurou);
System.out.println("吃货来吃吧");
//唤醒等待线程 (吃货)
rou.notify();
}
}
}
}
测试类
public class test {
public static void main(String[] args) {
Rou rou = new Rou();
Chihuo chihuo = new Chihuo("吃货", rou);
RouPu rouPu = new RouPu("肉铺", rou);
chihuo.start();
rouPu.start();
}
}
输出
:炸耗子肉
吃货来吃吧
吃货再吃炸耗子肉
肉铺开始杀猪
肉造好了:爆炒牛肉
吃货来吃吧
吃货再吃爆炒牛肉
肉铺开始杀猪
肉造好了:炸耗子肉
吃货来吃吧
吃货再吃炸耗子肉