Java多线程wait()用while代码块的理解

多线程wait()判断条件这个地方的逻辑琢磨了挺久的,本意就是拿到锁之后如果条件不满足就等待,感觉用if和while似乎没啥区别

首先是生产者,单独一个线程,和消费者共用一个锁和一个队列,都通过构造方法传入

1.线程里直接死循环持续生产消息,消息体最好不一样,可以弄个随机数,我这里用了微妙时间戳,调用add()方法

2.需要添加一个条件,如果队列已经满了,就不能继续添加了,调用wait()方法等待被其它线程消费了之后唤醒

3.生产完消息后,调用notifyAll()方法,唤醒等待的所有线程,直到synchronized全部执行完之后,释放lock锁供其它线程竞争来消费

package com.lihuia.multithreading;

import java.util.Queue;

/**
* Copyright (C), 2018-2019
* FileName: ProducerThread
* Author: lihui
* Date: 2019-09-09
*/

public class ProducerThread implements Runnable {

private Object lock;
private Queue<String> queue;
private final int MAX_SIZE = 5;

public ProducerThread(Object lock, Queue<String> queue) {
this.lock = lock;
this.queue = queue;
}

@Override
public void run() {
while (true) {
synchronized (lock) {
while (queue.size() >= MAX_SIZE) {
try {
System.out.println("队列已满,等待消费" + ",SIZE:" + queue.size());
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

String s = String.valueOf(System.nanoTime());
queue.add(s);
System.out.println("【生产】" + s + ",SIZE:" + queue.size());

lock.notifyAll();
}

}
}
}

同理消费者线程也是类似,锁和队列也是构造方法传入

1.线程里直接死循环持续消费消息,可以调用remove()方法

2.需要添加一个条件,如果队列已经空了,就不能继续消费了,调用wait()方法等待被其它线程生产了消息之后唤醒

3.消费了消息后,调用notifyAll()方法,唤醒等待的所有线程,直到synchronized全部执行完之后,释放lock锁供其它线程竞争来消费

package com.lihuia.multithreading;

import java.util.Queue;

/**
* Copyright (C), 2018-2019
* FileName: ConsumerThread
* Author: lihui
* Date: 2019-09-09
*/

public class ConsumerThread implements Runnable {

private Object lock;
private Queue<String> queue;

public ConsumerThread(Object lock, Queue<String> queue) {
this.lock = lock;
this.queue = queue;
}

@Override
public void run() {
while (true) {
synchronized (lock) {
while (queue.isEmpty()) {
try {
System.out.println("队列为空,等待生产" + ",SIZE:" + queue.size());
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("【消费】" + queue.remove() + ",SIZE:" + queue.size());

lock.notifyAll();
}
}
}
}

main方法测试类,只需要各自起一个线程,因为死循环不用结束

package com.lihuia.multithreading;

import java.util.LinkedList;
import java.util.Queue;

/**
* Copyright (C), 2018-2019
* FileName: TaskQueueTest
* Author: lihui
* Date: 2019-09-09
*/

public class MainTest {

public static void main(String[] args) {
Object lock = new Object();
Queue<String> queue = new LinkedList<>();
ProducerThread p = new ProducerThread(lock, queue);
ConsumerThread c = new ConsumerThread(lock, queue);

Thread producerThread = new Thread(p);
Thread consumerThread = new Thread(c);

producerThread.start();
consumerThread.start();
}
}

运行结果里截选了一段,可以看到的是当队列为空,下一个就是生产消息的线程;当队列已满,下一个就是消费消息的线程;队列非空未满的情况线程随意,这便是多线程wait()/notify()的作用

队列为空,等待生产,SIZE:0
【生产】457196908372693,SIZE:1
【生产】457196908377523,SIZE:2
【消费】457196908372693,SIZE:1
【消费】457196908377523,SIZE:0
队列为空,等待生产,SIZE:0
【生产】457196908402848,SIZE:1
【生产】457196908408694,SIZE:2
【生产】457196908411841,SIZE:3
【生产】457196908416546,SIZE:4
【生产】457196908427699,SIZE:5
队列已满,等待消费,SIZE:5
【消费】457196908402848,SIZE:4
【消费】457196908408694,SIZE:3
【消费】457196908411841,SIZE:2
【消费】457196908416546,SIZE:1
【消费】457196908427699,SIZE:0
【生产】457196908467660,SIZE:1
【生产】457196908474742,SIZE:2
【消费】457196908467660,SIZE:1
【消费】457196908474742,SIZE:0
队列为空,等待生产,SIZE:0
【生产】457196908506656,SIZE:1
【生产】457196908513924,SIZE:2
【生产】457196908516646,SIZE:3
【生产】457196908520747,SIZE:4
【生产】457196908523427,SIZE:5
【消费】457196908506656,SIZE:4
【消费】457196908513924,SIZE:3
【消费】457196908516646,SIZE:2
【消费】457196908520747,SIZE:1
【消费】457196908523427,SIZE:0
队列为空,等待生产,SIZE:0
【生产】457196908588817,SIZE:1
【生产】457196908595129,SIZE:2
【生产】457196908599816,SIZE:3
【生产】457196908610471,SIZE:4
【生产】457196908615401,SIZE:5

下面就是要研究的wait()方法,调用都放在了while而不是if里,比如在消费者线程里,启动该线程后,假如队列为空,等待生产者先生产消息,然后唤醒消费者消费,好像是一个if就可以搞定,其实不然

首先还是更直观看下wait和notify的流程

NewImage

然后假如消费者线程里用if而不是while来判断以及执行wait,如下

public void run() {
while (true) {
synchronized (lock) {
if (queue.isEmpty()) {
try {
System.out.println("队列为空,等待生产" + ",SIZE:" + queue.size());
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("【消费】" + queue.remove() + ",SIZE:" + queue.size());

lock.notifyAll();
}
}
}

假如只有一个生产者和一个消费者,队列空了,等待生产,生产好了唤醒消费,是没什么问题的;假如有多个消费者,比如consumer1和consumer2,当queue为空,consumer1里调用了wait等待,consumer2在synchronize处等待,等provider生产了一条消息后,notifyAll然后consumer2抢到了锁,消费了这条消息,这时候queue又空了,consumer2释放了锁之后,consumer1获取了锁,直接从wait()处执行,也不会进行queue.isEmpty()的判断,直接继续执行queue.remove()而报错,因为queue是空的

继续画一个直观的图

NewImage

问题的关键在于,执行了wait()方法的线程处于阻塞状态,而其它线程调用notifyAll()唤醒该阻塞状态的线程时,该线程是沿着wait()代码处继续往下执行的,因此如果用if的话,这个判断条件已经过了,不管满不满足条件,都会继续往后执行,从而消费了为空的队列;而假如用while,此刻wait()已经在while代码块里面了,都会重新进行一次条件的判断,如果队列为空不满足,那就继续wait()等待好了,如果队列不为空,才继续执行消费

发表回复