线程间的通信 waitnotify
Posted XeonYu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程间的通信 waitnotify相关的知识,希望对你有一定的参考价值。
上一篇:
线程同步之synchronized关键字
wait
wait跟sleep方法的作用一样,也是让线程休眠,但是针对的对象不同。
- sleep: sleep是Thread类中的静态方法,是直接作用于线程的,在哪个线程调sleep,哪个线程就休眠,而且可以随时调用。sleep方法执行期间不会释放锁,其他线程也必须要等待sleep执行完毕后才有机会进入同步代码中。
- wait: wait是Object类中的一个普通方法,通过对象调用。但是必须在持有该对象锁的同步代码中调用,否则会抛出异常。wait方法会释放锁,其他线程可以在wait期间进入同步代码中。
我们先来看看Object对象中的方法,本篇博客需要关心的就是以下红框中的几个方法。
再来看看wait源码
可以看到,最终调用了重载的 wait(long timeout)
我们先来简单用一用,看一下跟sleep的区别:
public static void main(String[] args) {
Object o = new Object();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
TimeUnit.SECONDS.sleep(2);
// o.wait(2000);
System.out.println(Thread.currentThread().getName() + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
}).start();
}
}
这里我们先在5个线程中的同步代码块中使用sleep看一下:
同样的代码,把sleep改成wait试一下,这要注意wait方法必须要在持有对象锁的同步代码块中调用,也就是说你想调用o.wait,那么你必须先锁住o。
public static void main(String[] args) {
Object o = new Object();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
// TimeUnit.SECONDS.sleep(2);
o.wait(2000);
System.out.println(Thread.currentThread().getName() + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
}).start();
}
}
运行结果如下:
对比两个运行结果,可以很明显的看出来,sleep是不会释放锁的,休眠期间其他线程必须等待锁被释放后才能执行,wait是会释放锁的,其他线程会在wait期间走进同步代码中。
上面我们是给wait指定了等待时间,当我们不给wait指定时间时,则线程会一直处于等待状态,直到调用了同一个对象锁的notify或notifyAll方法,才会继续执行。
示例代码:
不给wait指定等待时间
public static void main(String[] args) {
Object o = new Object();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
o.wait();
System.out.println(Thread.currentThread().getName() + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
}).start();
}
}
运行结果:
可以看到,后面一直都没执行。
notify
源码注释太长,这里就不贴了。
我们只需要记住,notify是用来唤醒正在等待的单个线程,如果同一把锁有多个线程都在等待,则随机唤醒其中一个。
我们来简单用一用:
public static void main(String[] args) {
Object o = new Object();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
o.wait();
System.out.println(Thread.currentThread().getName() + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
}).start();
}
/*让主线程休眠2秒钟*/
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o) {
o.notify();
}
}
看一下运行结果:
可以看到,只有一条线程被唤醒了。
notifyAll
notify是用来唤醒正在等待的所有线程,如果同一把锁有多个线程都在等待,则唤醒所有线程。
来用一把:
public static void main(String[] args) {
Object o = new Object();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
o.wait();
System.out.println(Thread.currentThread().getName() + "被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
}).start();
}
/*让主线程休眠2秒钟*/
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o) {
o.notifyAll();
}
}
运行结果如下:
可以看到,所有wait的线程都被唤醒了。
实际上,Thread中的join方法底层调用的就是wait方法,join源码如下,join本身就是个同步方法。
那join方法是什么时候会被唤醒呢?
通过注释可以知道,是在线程处于结束状态后就会被唤醒。
之前我们在 从Java线程到kotlin协程之线程合并 (join) 这篇文章中我们用join来实现两个接口
那这样一来,我们可以使用wait来达到跟join一样的效果,实现线程的同步。
代码如下:
先来创建两个Runnable来模拟请求接口
/**
* 模拟请求高德接口
*/
class GaoDeRunnable implements Runnable {
/*模拟的锁对象*/
Object lock = null;
/*接口返回的结果*/
private String result = "";
public GaoDeRunnable(Object lock) {
this.lock = lock;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
@Override
public void run() {
/*模拟请求高德接口*/
synchronized (lock) {
System.out.println("高德请求开始执行....");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = "gaodeResult";
System.out.println("高德接口请求完毕,唤醒等待的线程");
/*执行完毕 唤醒等待的线程*/
lock.notifyAll();
}
}
}
/**
* 模拟请求百度接口
*/
class BaiDuRunnable implements Runnable {
Object lock = null;
private String result = "";
public BaiDuRunnable(Object lock) {
this.lock = lock;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
@Override
public void run() {
/*模拟请求高德接口*/
synchronized (lock) {
System.out.println("百度请求开始执行....");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = "baiduResult";
System.out.println("百度接口请求完毕,唤醒等待的线程");
lock.notifyAll();
}
}
}
两个代码逻辑都一样,休眠一段时间后,唤醒wait的线程。
main方法代码如下:
public static void main(String[] args) {
/*高德 锁*/
Object gaodeObjLock = new Object();
/*百度 锁*/
Object baiduObjLock = new Object();
/*模拟请求高德接口的Runnable*/
GaoDeRunnable gaoDeRunnable = new GaoDeRunnable(gaodeObjLock);
/*模拟请求百度接口的Runnable*/
BaiDuRunnable baiDuRunnable = new BaiDuRunnable(baiduObjLock);
/*启动两个线程去执行*/
new Thread(gaoDeRunnable).start();
new Thread(baiDuRunnable).start();
/*等待高德Runnable返回数据*/
synchronized (gaodeObjLock) {
while (gaoDeRunnable.getResult().isEmpty()) {
try {
gaodeObjLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*高德接口执行完毕*/
System.out.println("高德接口执行完毕,获取的结果是:" + gaoDeRunnable.getResult());
}
/*等待百度Runnable返回数据*/
synchronized (baiduObjLock) {
while (baiDuRunnable.getResult().isEmpty()) {
try {
baiduObjLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*高德接口执行完毕*/
System.out.println("百度接口执行完毕,获取的结果是:" + baiDuRunnable.getResult());
}
System.out.println("两个接口都执行完毕了,最终的数据是:" + gaoDeRunnable.getResult() + "-----" + baiDuRunnable.getResult());
}
下面来看看运行效果:
可以看到,运行结果跟之前使用join是一样的。
好了,wait方法就是这些
下一篇:
如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!
以上是关于线程间的通信 waitnotify的主要内容,如果未能解决你的问题,请参考以下文章
面试题:线程A打印1-10数字,打印到第5个数字时,通知线程B