线程间的协作(wait/notify/sleep/yield/join)
1、线程的状态
Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态)。
New:新建状态,当线程创建完成时为新建状态,即new Thread(...),还没有调用start方法时,线程处于新建状态。
Runnable:就绪状态,当调用线程的的start方法后,线程进入就绪状态,等待CPU资源。处于就绪状态的线程由Java运行时系统的线程调度程序(thread scheduler)来调度。
Running:运行状态,就绪状态的线程获取到CPU执行权以后进入运行状态,开始执行run方法。
Blocked:阻塞状态,线程没有执行完,由于某种原因(如,I/O操作等)让出CPU执行权,自身进入阻塞状态。
Dead:死亡状态,线程执行完成或者执行过程中出现异常,线程就会进入死亡状态。
这五种状态之间的转换关系如下图所示:
有了对这五种状态的基本了解,现在我们来看看Java中是如何实现这几种状态的转换的。
2、等待和通知的标志范式
等待方:
- 获取对象的锁;
- 循环里判断条件是否满足,不满足调用wait方法,
- 条件满足执行业务逻辑
通知方:
- 获取对象的锁;
- 改变条件
- 通知所有等待在对象的线程
2、1 wait/notify/notifyAll API。
2、1、1 wait
void wait() | Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. |
void wait(long timeout) | Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed. |
void wait(long timeout, int nanos) | Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed |
- 调用wait方法能将当前线程阻塞掉,直到调用notify或者notifyAll方法来让线程重新去尝试获取锁(即唤醒线程)。
- wait方法是一个本地方法,它是通过一个monitor对象锁来实现的,只有拥有了该对象的监视器锁才能调用wait方法,那么怎么调用wait方法呢?
- 是通过增加synchronized关键字来实现的,这也是为什么wait必须在synchronized修饰的代码中运行的原因。但只要调用了wait方法,monitor锁就会被马上释放掉。
void notify() | Wakes up a single thread that is waiting on this object's monitor. |
void notifyAll() | Wakes up all threads that are waiting on this object's monitor. |
有了对wait方法原理的理解,notify方法和notifyAll方法就很容易理解了。既然wait方式是通过对象的monitor对象来实现的,所以只要在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。notify和notifyAll的区别在于前者(容易信号丢失,达不到预期效果,下面有例子)只能唤醒monitor上的一个线程,对其他线程没有影响,而notifyAll则唤醒所有的线程.
2、1 测试wait/notify/notifyAll 代码准备
package org.hxm.thread.demo2; /** * @author : Aaron * * create at: 2020/9/7 15:28 * * description: 快递实体 */ public class Express { public final static String CITY = "ShangHai"; private int km;/*快递运输里程数*/ private String site;/*快递到达地点*/ public Express() { } public Express(int km, String site) { this.km = km; this.site = site; } /* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/ public synchronized void changeKm(){ this.km = 101; notifyAll(); //其他的业务代码 } /* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/ public synchronized void changeSite(){ this.site = "BeiJing"; notifyAll(); } public synchronized void waitKm(){ while(this.km<=100) { try { wait(); System.out.println("check km thread["+Thread.currentThread().getId() +"] is be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("the km is"+this.km+",I will change db.");//此处执行的是业务代码 } public synchronized void waitSite(){ while(CITY.equals(this.site)) { try { wait(); System.out.println("check site thread["+Thread.currentThread().getId() +"] is be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("the site is"+this.site+",I will call user.");//此处执行的是业务代码
} }
package org.hxm.thread.demo2; /** * @author : Aaron * * create at: 2020/9/7 15:45 * * description: 测试wait/notify/notifyAll */ public class TestWN { private static Express express = new Express(0,Express.CITY); /*检查里程数变化的线程,不满足条件,线程一直等待*/ private static class CheckKm extends Thread{ @Override public void run() { express.waitKm(); } } /*检查地点变化的线程,不满足条件,线程一直等待*/ private static class CheckSite extends Thread{ @Override public void run() { express.waitSite(); } } public static void main(String[] args) throws InterruptedException { for(int i=0;i<3;i++){//三个线程 new CheckSite().start(); } for(int i=0;i<3;i++){//里程数的变化 new CheckKm().start(); } Thread.sleep(1000); express.changeSite();//快递地点变化 } }
2、2 notifyAll 结果展示
check km thread[15] is be notifed. check km thread[14] is be notifed. check km thread[13] is be notifed. check site thread[12] is be notifed. the site isBeiJing,I will call user. check site thread[11] is be notifed. the site isBeiJing,I will call user. check site thread[10] is be notifed. the site isBeiJing,I will call user.
通过结果 我们发现,监听 地址和公里数的线程都被唤醒
2、3 notify 结果展示,此处是改变千米数
public static void main(String[] args) throws InterruptedException { for(int i=0;i<3;i++){//三个线程 new CheckSite().start(); } for(int i=0;i<3;i++){//里程数的变化 new CheckKm().start(); } Thread.sleep(1000); express.changeKm();//快递地点变化 }
打印结果
check site thread[10] is be notifed.
发现唤醒了 waitSite,并没有唤醒waitKm
2、4 结论
使用notify因为有可能发生信号丢失的的情况,所以 尽量实用化notifyAll
3、join的使用
join是线程让步的一种方式。线程A,执行了线程B的join方法,线程A必须要等待B执行完成了以后,线程A才能继续自己的工作
package org.hxm.thread.demo2; import org.hxm.thread.utils.ThreadTools; /** * @author : Aaron * * create at: 2020/9/7 22:04 * * description: 学习join */ public class Usejoin { public static class JumpQueue implements Runnable { private Thread thread; //插队的线程 public JumpQueue(Thread thread) { this.thread = thread; } @Override public void run() { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " is terminted."); } } public static void main(String[] args) { Thread previous = Thread.currentThread();//当前是主线程 for (int i = 0; i < 10; i++) { //i =0 previous是主线程, i=1是,previous是i=0这个线程 Thread t = new Thread(new JumpQueue(previous), "ThreadName" + String.valueOf(i)); t.start(); System.out.println(String.format("线程【%S】插队插到了 [%S]的线程前面", previous.getName(), t.getName())); previous = t; } ThreadTools.second(2); System.out.println(String.format("当前线程终止 【%s】", Thread.currentThread().getName())); } }
打印结果
线程【MAIN】插队插到了 [THREADNAME0]的线程前面
线程【THREADNAME0】插队插到了 [THREADNAME1]的线程前面
线程【THREADNAME1】插队插到了 [THREADNAME2]的线程前面
线程【THREADNAME2】插队插到了 [THREADNAME3]的线程前面
线程【THREADNAME3】插队插到了 [THREADNAME4]的线程前面
线程【THREADNAME4】插队插到了 [THREADNAME5]的线程前面
线程【THREADNAME5】插队插到了 [THREADNAME6]的线程前面
线程【THREADNAME6】插队插到了 [THREADNAME7]的线程前面
线程【THREADNAME7】插队插到了 [THREADNAME8]的线程前面
线程【THREADNAME8】插队插到了 [THREADNAME9]的线程前面
当前线程终止 【main】
ThreadName0 is terminted.
ThreadName1 is terminted.
ThreadName2 is terminted.
ThreadName3 is terminted.
ThreadName4 is terminted.
ThreadName5 is terminted.
ThreadName6 is terminted.
ThreadName7 is terminted.
ThreadName8 is terminted.
ThreadName9 is terminted.
由此可以看出,A线程调用b线程的join方法吗,表示的是A在b线程执行结束后才执行。