写点什么

Java 并发编程系列——线程的等待与唤醒

用户头像
孙苏勇
关注
发布于: 2020 年 04 月 29 日
Java并发编程系列——线程的等待与唤醒

之前写了Java的线程启动方式,这篇写写线程的等待与唤醒。



我们在使用线程时,常需要根据某些条件来触发线程做一些相关的工作,这个实现如果用轮询的方式来做能实现,但如果存在大量线程进行轮询,及时性无法保障,也会损耗资源。更合适的方式是使用等待与唤醒。



等待使用wait()方法,唤醒使用notifyAll(),这里先留个问题,唤醒还有个方法是notify(),为什么这里只说了notifyAll()。这个在后续代码演示时再细说。



首先先看下等待唤醒的一般实现方式,我们分别从等待方和唤醒方来分别看下。

等待一般模式

synchronized {
while(!condition) {
wait();
}
doSomeThing();
}



唤醒一般模式

synchronized {
changeCondition();
notifyAll();
}



接下来用一个完整的示例来说明一下,定义一个演示类,其中定义两个变量,分别用于两个不同的条件判断,然后分别建立三个线程用于启动对变量的检测,并进入等待状态,最后通过一系列操作改变条件,来说明程序的执行情况。



代码如下:

public class ShowNotify {
public static class Show {
private int condition1 = 0;
private boolean condition2 = false;
public synchronized void checkCondition1() {
while (this.condition1 <= 10) {
try {
System.out.printf("thread %d start check conditions1 now\n",
Thread.currentThread().getId());
wait();
System.out.printf("condition1 is %d, waiting in thread %d\n",
this.condition1, Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("condition1 is %d now, bigger than 10, thread %d over\n",
this.condition1, Thread.currentThread().getId());
}
public synchronized void checkCondition2() {
while (!condition2) {
try {
System.out.printf("thread %d start check conditions2 now\n",
Thread.currentThread().getId());
wait();
System.out.printf("condition2 is false, waiting in thread %d\n",
Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("condition2 is true now, thread %d over\n",
Thread.currentThread().getId());
}
public synchronized void changeCondition1(int condition1) {
this.condition1 = condition1;
notifyAll();
}
public synchronized void changeCondition2(boolean condition2) {
this.condition2 = condition2;
notifyAll();
}
}
public static void main(String[] args) throws InterruptedException {
Show show = new Show();
for (int i = 0; i < 3; i++) {
Thread condition2Runner = new Thread(new Runnable() {
@Override
public void run() {
show.checkCondition2();
}
});
condition2Runner.start();
}
for (int i = 0; i < 3; i++) {
Thread condition1Runner = new Thread(new Runnable() {
@Override
public void run() {
show.checkCondition1();
}
});
condition1Runner.start();
}
Thread.sleep(2000);
System.out.println();
show.changeCondition1(8);
Thread.sleep(1000);
System.out.println();
show.changeCondition1(11);
Thread.sleep(1000);
System.out.println();
show.changeCondition2(true);
Thread.sleep(1000);
}
}

运行结果如下:

thread 12 start check conditions2 now
thread 17 start check conditions1 now
thread 16 start check conditions1 now
thread 15 start check conditions1 now
thread 13 start check conditions2 now
thread 14 start check conditions2 now

condition2 is false, waiting in thread 12
thread 12 start check conditions2 now
condition2 is false, waiting in thread 14
thread 14 start check conditions2 now
condition2 is false, waiting in thread 13
thread 13 start check conditions2 now
condition1 is 8, waiting in thread 15
thread 15 start check conditions1 now
condition1 is 8, waiting in thread 16
thread 16 start check conditions1 now
condition1 is 8, waiting in thread 17
thread 17 start check conditions1 now

condition2 is false, waiting in thread 12
thread 12 start check conditions2 now
condition1 is 11, waiting in thread 17
condition1 is 11 now, bigger than 10, thread 17 over
condition1 is 11, waiting in thread 16
condition1 is 11 now, bigger than 10, thread 16 over
condition1 is 11, waiting in thread 15
condition1 is 11 now, bigger than 10, thread 15 over
condition2 is false, waiting in thread 13
thread 13 start check conditions2 now
condition2 is false, waiting in thread 14
thread 14 start check conditions2 now

condition2 is false, waiting in thread 12
condition2 is true now, thread 12 over
condition2 is false, waiting in thread 14
condition2 is true now, thread 14 over
condition2 is false, waiting in thread 13
condition2 is true now, thread 13 over



从运行结果可以看出当第一次改变条件唤醒各线程时,条件均不满足,所以线程都重新进入等待状态,接着改变第一个条件使其满足要求,看到线程都被唤醒,但仅和条件一相关的线程满足了条件,执行(这里执行后就结束),和条件二相关的则依然进入等待状态,最后改变条件二也满足要求,剩余的与条件二相关的线程唤醒并执行(这里执行后就结束)



之前我们提了一个问题,为什么不用notify()。这里我们就将代码中的notifyAll()替换为notify()再看下输出结果,如下:

thread 12 start check conditions2 now
thread 13 start check conditions2 now
thread 17 start check conditions1 now
thread 16 start check conditions1 now
thread 15 start check conditions1 now
thread 14 start check conditions2 now

condition2 is false, waiting in thread 12
thread 12 start check conditions2 now

condition2 is false, waiting in thread 13
thread 13 start check conditions2 now

condition1 is 11, waiting in thread 17
condition1 is 11 now, bigger than 10, thread 17 over



可以看到无法得到我们希望的结果,notify()只会唤醒一个等待的线程,而且唤醒的是哪个线程也无法保证



很多时候,我们不希望线程无休止地等待条件满足,而希望它在一定时间后不论条件满足与否都执行相关操作,这在实现上就要借助wait(timeout)了。



等待超时的一般模式:

synchronized check(timeout) {
timeUntil = now() + timeout;
remain = timeout;
while(!condtion && remain > 0) {
wait();
remain = timeUntil - now();
}
doSomeThing();
}



仍然用一段代码来展示,我们设置一个用于判断的条件,另外对于timeout的设置,我们认为设置<=0的数值表示不超时,设置>0的表示需要超时,这样实质上相当于包含了之前示例的情况。然后我们依然分别启动两组线程,一组不设超时,一组设置超时,来观察其执行情况。



代码如下:

public class ShowWaitTimeOut {
public static class Show {
private int condition = 0;
public synchronized void triggerCondition(int condition) {
this.condition = condition;
notifyAll();
}
public synchronized void checkCondition(long timeoutMillis) {
if (timeoutMillis <= 0) {
while (this.condition <= 10) {
try {
System.out.printf("thread %d start check conditions without timeout\n",
Thread.currentThread().getId());
wait();
System.out.printf("condition is %d, waiting in thread %d\n",
this.condition, Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("condition is %d now, bigger than 10, thread %d over\n",
this.condition, Thread.currentThread().getId());
} else {
long timeUntil = System.currentTimeMillis() + timeoutMillis;
long remain = timeoutMillis;
while (this.condition <= 10 && remain > 0) {
try {
System.out.printf("thread %d start check conditions with timeout\n",
Thread.currentThread().getId());
wait(remain);
remain = timeUntil - System.currentTimeMillis();
System.out.printf("condition is %d, waiting in thread %d\n",
this.condition, Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("condition is %d now, thread %d timeout\n",
this.condition, Thread.currentThread().getId());
}
}
}
public static void main(String[] args) throws InterruptedException {
Show show = new Show();
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
show.checkCondition(0);
}
});
thread.start();
}
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
show.checkCondition(2000);
}
});
thread.start();
}
Thread.sleep(1000);
System.out.println();
show.triggerCondition(8);
Thread.sleep(4000);
System.out.println();
show.triggerCondition(12);
}
}



执行结果如下:

thread 12 start check conditions without timeout
thread 13 start check conditions without timeout
thread 17 start check conditions with timeout
thread 16 start check conditions with timeout
thread 15 start check conditions with timeout
thread 14 start check conditions without timeout

condition is 8, waiting in thread 12
thread 12 start check conditions without timeout
condition is 8, waiting in thread 14
thread 14 start check conditions without timeout
condition is 8, waiting in thread 15
thread 15 start check conditions with timeout
condition is 8, waiting in thread 16
thread 16 start check conditions with timeout
condition is 8, waiting in thread 17
thread 17 start check conditions with timeout
condition is 8, waiting in thread 13
thread 13 start check conditions without timeout
condition is 8, waiting in thread 17
condition is 8 now, thread 17 timeout
condition is 8, waiting in thread 16
condition is 8 now, thread 16 timeout
condition is 8, waiting in thread 15
condition is 8 now, thread 15 timeout

condition is 12, waiting in thread 12
condition is 12 now, bigger than 10, thread 12 over
condition is 12, waiting in thread 13
condition is 12 now, bigger than 10, thread 13 over
condition is 12, waiting in thread 14
condition is 12 now, bigger than 10, thread 14 over



从执行结果上可以看出第一次改变条件,唤醒所有线程,都不满足条件,也不超时,此时所有线程重新进入等待状态,接着超时时间到,几个设置超时的线程被唤醒并执行(这里执行后结束),最后改变条件,剩余线程被唤醒且由于满足条件线程执行相关操作(这里执行后结束)



这样我们对于线程的等待与唤醒就有了大致的了解,下一次回顾,打算写一下几个常用的线程池。



本系列其他文章:

Java并发编程系列插曲——对象的内存结构

Java并发编程系列——线程



发布于: 2020 年 04 月 29 日阅读数: 149
用户头像

孙苏勇

关注

不读书,思想就会停止。 2018.04.05 加入

公众号“像什么",记录想记录的。

评论

发布
暂无评论
Java并发编程系列——线程的等待与唤醒