Java 等待和通知

原文:https://www.studytonight.com/java-examples/java-wait-and-notify

多线程是同时运行多个线程的过程。多线程提高了我们代码的效率,因为多个线程将问题分开,同时处理较小的部分。然而,多个线程可能需要访问一个公共资源。

在这个公共资源上使用锁来避免并行修改。当线程没有同步到使用这个锁时,就会出现不一致。在 Java 中wait()notify()方法用于线程通信和同步。

让我们更多地了解这些方法。

Java 等待()方法

Java 中的Object类有一个最终的wait()方法。它用于暂停线程的执行。它还会使线程放弃它的锁,以便其他线程可以访问临界区。代码中修改共享资源的部分称为临界区。

此方法有三个重载签名。

  • 等待()
  • 等待(长时间超时)
  • 等待(长超时,整数纳秒)

如果我们指定超时持续时间,那么线程将在超时后自动唤醒。如果没有提到超时,那么线程必须等待其他线程对其调用 notify。

使用wait()方法进行同步的一般语法如下所示。

synchronized(object)
{ 
    while(condition is false)
    { 
        object.wait();
    }

    //do the task
}

Java notify()方法

notify()方法也属于 Object 类。此方法用于唤醒等待的线程。如果多个线程正在等待,那么随机选择的线程将被唤醒。

请注意,此方法不会使调用(或通知)线程放弃其锁。

Java notifyAll()方法

notifyAll()方法与 notify()方法非常相似,但是它将唤醒所有等待的线程,而不是只唤醒一个线程。

为什么 wait()被包含在 while 循环中?

wait()方法应该包含在 while 循环中,因为线程可以在没有notify()调用的情况下被唤醒。这被称为虚假唤醒。while 循环的另一个原因是一个邪恶的线程可能会调用 notifyAll()并唤醒所有等待的线程。

线程需要检查条件,如果条件不满足,那么它们应该继续等待。

例子:生产者和消费者问题

让我们通过一个例子来了解如何使用 wait()和 notify()。我们将用这些方法解决传统的生产者-消费者问题。我们先来了解生产者-消费者问题。

  • 这个问题涉及到生产者和消费者。
  • 生产者将生产物品并将它们添加到缓冲区。
  • 消费者将消费来自缓冲区的产品。
  • 如果缓冲区已满,生产者不应生产新项目。
  • 如果缓冲区为空,使用者不应使用。

让我们为生产者类编写代码。这个类将实现可运行的接口。它将有一个名为缓冲区的成员。

当缓冲区已满时,生产者应调用wait()方法。缓冲区的最大容量设置为 5 个元素。在生成一个新项目并将其添加到缓冲区后,它应该通知使用者线程。如果消费线程正在等待,那么它将醒来并开始消费。

class Producer implements Runnable
{
    //buffer to store the produced items
    private final LinkedList<Integer> buffer;    
    Producer(LinkedList<Integer> buffer)
    {
        this.buffer = buffer;
    }
    @Override
    public void run()
    {        
        //Infinitely produce items
        while(true)
        {
            try {
            this.produce();
            }
            catch(Exception e) {
                System.out.print(e);
            }
        }
    }    
    public void produce() throws InterruptedException
    {
        synchronized(buffer)
        {
            //If the buffer is full then wait
            while(buffer.size() == 5)
            {
                System.out.println("Producer is waiting");
                buffer.wait();
            }            
            //Produce a new random number
            Random r = new Random();
            int num = r.nextInt(100);
            System.out.println("Producer produced: " + num);
            buffer.add(num);
            buffer.notifyAll();
            Thread.sleep(10);
        }
    }
}

如果缓冲区为空,消费者类应该等待。当它获取锁时,使用者应该从缓冲区弹出第一个元素并使用它。完成后,使用者应该通知生产者线程。

class Consumer implements Runnable
{
    //buffer to consume items from
    private final LinkedList<Integer> buffer;    
    Consumer(LinkedList<Integer> buffer)
    {
        this.buffer = buffer;
    }    
    @Override
    public void run()
    {
        //Infinitely consume
        while(true)
        {
            try {
                this.consume();
            }
            catch(Exception e) {
                System.out.print(e);
            }
        }
    }    
    public void consume() throws InterruptedException
    {
        synchronized(buffer)
        {
            //Wait if the buffer is empty
            while(buffer.size() == 0)
            {
                System.out.println("Consumer is waiting");
                buffer.wait();
            }

            //Consume the first item from the buffer
            int num = buffer.remove(0);
            System.out.println("Consumer consumed: " + num);
            buffer.notifyAll();
            Thread.sleep(5);
        }
    }
}

请注意,我们使用同步块来确保一次只有一个线程可以访问缓冲区。

下面显示了上述方法的演示。

public class WaitNotifyDemo
{
    public static void main(String args[])
    {
        LinkedList<Integer> buffer = new LinkedList<>();
        Producer producer = new Producer(buffer);
        Consumer consumer = new Consumer(buffer);

        Thread p = new Thread(producer);
        Thread c = new Thread(consumer);

        p.start();
        c.start();    
    }
}

生产者生产:26 生产者生产:37 消费者消费:26 消费者消费:37 消费者等待 生产者生产:66 生产者生产:91 生产者生产:51 生产者生产:88 生产者生产:61 生产者等待

摘要

wait(), notify()notifyAll()方法用于线程通信和同步。

在本教程中,我们学习了如何使用这些方法。我们还学习了如何使用这些方法解决生产者-消费者问题。请注意,这些是传统的方法,使用起来比新的 API 复杂一点。