📜  Java中的线程间通信

📅  最后修改于: 2022-05-13 01:55:34.889000             🧑  作者: Mango

Java中的线程间通信

先决条件: Java中的多线程, Java中的同步

Java中的线程间通信是一种机制,其中一个线程在其临界区暂停运行,而另一个线程被允许进入(或锁定)在同一临界区执行。

什么是轮询,它有什么问题?

反复测试条件直到它变为真的过程称为轮询。轮询通常是在循环的帮助下实现的,以检查特定条件是否为真。如果是真的,就会采取某种行动。这浪费了许多 CPU 周期,并使实现效率低下。

例如,在一个经典的排队问题中,一个线程正在生产数据,而另一个线程正在使用它。

Java多线程如何解决这个问题?

为了避免轮询, Java使用了三种方法,即wait()、notify() 和 notifyAll()。所有这些方法都属于最终的对象类,因此所有类都有它们。它们只能在同步块中使用。

  • wait():它告诉调用线程放弃锁并进入睡眠状态,直到其他线程进入同一个监视器并调用 notify()。
  • notify():它在同一个对象上唤醒一个名为 wait() 的线程。需要注意的是,调用 notify() 不会放弃对资源的锁定。
  • notifyAll():唤醒同一个对象上所有调用 wait() 的线程。

例子:

一个简单的Java程序来演示这三种方法。请注意,该程序可能仅在离线 IDE 中运行,因为它包含在多个点上接受输入。

Java
// Java program to demonstrate inter-thread communication
// (wait(), join() and notify())
  
import java.util.Scanner;
  
public class threadexample
{
    public static void main(String[] args) throws InterruptedException
    {
        final PC pc = new PC();
  
        // Create a thread object that calls pc.produce()
        Thread t1 = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    pc.produce();
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        });
  
        // Create another thread object that calls
        // pc.consume()
        Thread t2 = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    pc.consume();
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        });
  
        // Start both threads
        t1.start();
        t2.start();
  
        // t1 finishes before t2
        t1.join();
        t2.join();
    }
  
    // PC (Produce Consumer) class with produce() and
    // consume() methods.
    public static class PC
    {
        // Prints a string and waits for consume()
        public void produce()throws InterruptedException
        {
            // synchronized block ensures only one thread
            // running at a time.
            synchronized(this)
            {
                System.out.println("producer thread running");
  
                // releases the lock on shared resource
                wait();
  
                // and waits till some other method invokes notify().
                System.out.println("Resumed");
            }
        }
  
        // Sleeps for some time and waits for a key press. After key
        // is pressed, it notifies produce().
        public void consume()throws InterruptedException
        {
            // this makes the produce thread to run first.
            Thread.sleep(1000);
            Scanner s = new Scanner(System.in);
  
            // synchronized block ensures only one thread
            // running at a time.
            synchronized(this)
            {
                System.out.println("Waiting for return key.");
                s.nextLine();
                System.out.println("Return key pressed");
  
                // notifies the produce thread that it
                // can wake up.
                notify();
  
                // Sleep
                Thread.sleep(2000);
            }
        }
    }
}


输出
producer thread running
Waiting for return key.
Return key pressed
Resumed

解释:

尽管看起来很可怕,但如果你经历两次,它就是小菜一碟。

  1. 在主类中,创建了一个新的 PC 对象。
  2. 它使用两个不同的线程(即 t1 和 t2)运行 PC 对象的生产和消费方法,并等待这些线程完成。

让我们了解一下我们的生产和消费方法是如何工作的。

  • 首先,使用同步块可确保一次只运行一个线程。此外,由于在消费循环开始时有一个 sleep 方法,因此生产线程会启动。
  • 当在生产方法中调用等待时,它会做两件事。首先,它释放它在 PC 对象上持有的锁。其次,它使生产线程进入等待状态,直到所有其他线程都终止。它可以再次获取 PC 对象上的锁,并且某些其他方法通过在同一对象上调用 notify 或 notifyAll 将其唤醒。
  • 因此我们看到,只要调用了等待,控制就转移到消费线程,并打印 - “等待返回键”。
  • 当我们按下返回键后,consume 方法调用 notify()。它还做了两件事——首先,与 wait() 不同,它不会释放共享资源的锁,因此为了获得所需的结果,建议仅在方法结束时使用 notify。其次,它通知等待线程他们现在可以唤醒,但只有在当前方法终止之后。
  • 正如您可能已经观察到的那样,即使在通知之后,控制也不会立即传递给生产线程。原因是我们在 notify() 之后调用了 Thread.sleep()。我们已经知道消费线程持有一个 PC 对象的锁。另一个线程在释放锁之前无法访问它。因此,只有在消费线程完成其休眠时间并自行终止之后,生产线程才能收回控制权。
  • 停顿 2 秒后,程序终止完成。

如果您仍然对我们在消费线程中使用通知的原因感到困惑,请尝试删除它并再次运行您的程序,因为您现在必须注意到程序永远不会终止。

原因很简单。当您调用等待生产线程时,它继续等待并且从未终止。由于一个程序一直运行到它的所有线程都终止了,所以它会一直运行下去。

有第二种方法可以解决这个问题。您可以使用 wait() 的第二种变体。

void wait(long timeout) 

这将使调用线程仅在指定的时间内休眠。