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

Posted 这瓜保熟么

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的流程

然后假如消费者线程里用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是空的

继续画一个直观的图

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

以上是关于Java多线程wait()用while代码块的理解的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程为什么使用while循环来调用wait方法

Java多线程为什么使用while循环来调用wait方法

Java多线程为什么使用while循环来调用wait方法

java中多线程中测试某个条件的变化用 if 还是用 while?

多线程中wait和notify的特性

Java:多线程的同步方式和锁