提问者:小点点

使用java同步理解生产者-消费者


我一直在研究PC问题,以了解Java同步和线程间通信。使用底部的代码,输出是

Producer produced-0
Producer produced-1
Producer produced-2
Consumer consumed-0
Consumer consumed-1
Consumer consumed-2
Producer produced-3
Producer produced-4
Producer produced-5
Consumer consumed-3
Consumer consumed-4

但是输出不应该像下面这样

Producer produced-0
Consumer consumed-0
Producer produced-1
Consumer consumed-1
Producer produced-2
Consumer consumed-2
Producer produced-3

我期望这样的输出,因为我的理解是,当方法终止时,一旦产生方法释放锁,消费者就会被通知产生的值。结果,等待的消费者块进入同步状态获取锁来消费产生的值,同时生产者方法被阻塞。这个锁在消费方法结束时被释放,该消费方法被生产者线程获取,该线程由于同步而被阻塞,并且随着每个方法由于获取的锁而被阻塞,循环继续。

请让我知道我误解了什么?谢谢

package MultiThreading;

//Java program to implement solution of producer
//consumer problem.
import java.util.LinkedList;

public class PCExample2
{
 public static void main(String[] args)
                     throws InterruptedException
 {
     // Object of a class that has both produce()
     // and consume() methods
     final PC pc = new PC();

     // Create producer thread
     Thread t1 = new Thread(new Runnable()
     {
         @Override
         public void run()
         {
             try
             {
                 while (true) {
                     pc.produce();   
                 }                 
             }
             catch(InterruptedException e)
             {
                 e.printStackTrace();
             }
         }
     });

     // Create consumer thread
     Thread t2 = new Thread(new Runnable()
     {
         @Override
         public void run()
         {
             try
             {
                 while (true) {
                     pc.consume();   
                 }
             }
             catch(InterruptedException e)
             {
                 e.printStackTrace();
             }
         }
     });

     // Start both threads
     t1.start();
     t2.start();

     // t1 finishes before t2
     t1.join();
     t2.join();
 }

 // This class has a list, producer (adds items to list
 // and consumber (removes items).
 public static class PC
 {
     // Create a list shared by producer and consumer
     // Size of list is 2.
     LinkedList<Integer> list = new LinkedList<>();
     int capacity = 12;
     int value = 0;

     // Function called by producer thread
     public void produce() throws InterruptedException
     {         
         synchronized (this)
         {
             // producer thread waits while list
             // is full
             while (list.size()==capacity)
                 wait();

             System.out.println("Producer produced-"
                                           + value);

             // to insert the jobs in the list
             list.add(value++);

             // notifies the consumer thread that
             // now it can start consuming
             notify();

             // makes the working of program easier
             // to  understand
             Thread.sleep(1000);
         }
     }

     // Function called by consumer thread
     public void consume() throws InterruptedException
     {
         synchronized (this)
         {
             // consumer thread waits while list
             // is empty
             while (list.size()==0)
                 wait();

             //to retrive the ifrst job in the list
             int val = list.removeFirst();


             System.out.println("Consumer consumed-"
                                             + val);

             // Wake up producer thread
             notify();

             // and sleep
             Thread.sleep(1000);
         }
     }
 }
}

共3个答案

匿名用户

不一定是第一个调用当前获取的锁的线程(我们称之为线程A)会在锁的当前所有者线程放弃锁时立即获取锁,如果其他线程也在线程A尝试获取锁后调用了锁。没有有序的“队列”。请参阅此处和此处。因此,从程序的输出来看,似乎在生产者释放锁之后,消费者在之前可能没有足够的时间获取锁,而生产者线程中的循环被重复,生产者线程再次调用锁(正如其他答案所指出的,Thread.睡眠()不会导致睡眠线程放弃锁),如果消费者运气不好,生产者将重新获取锁,即使消费者在那里。

要真正确保两个线程交替修改PC,您必须让生产者线程仅在列表大小大于零时等待,而不是在列表包含12个(或更多)元素时等待。

匿名用户

从API:被唤醒的线程将以通常的方式与可能主动竞争此对象同步的任何其他线程竞争;例如,被唤醒的线程在成为下一个锁定此对象的线程时没有可靠的特权或劣势。

睡眠()移动到同步块之外,以使另一个线程具有获取锁的优势。

匿名用户

注意两种方式:通知

对象通知():

唤醒正在等待此对象监视器的单个线程。如果任何线程正在等待此对象,则选择其中一个被唤醒。选择是任意的,由实现自行决定。线程通过调用其中一个等待方法在对象的监视器上等待。
被唤醒的线程将无法继续,直到当前线程放弃对该对象的锁定。被唤醒的线程将以通常的方式与任何其他可能积极竞争同步此对象的线程竞争;例如,被唤醒的线程在成为下一个锁定此对象的线程时没有可靠的特权或劣势。

线程():

导致当前正在执行的线程在指定的毫秒数加上指定的纳秒数Hibernate(暂时停止执行),以系统计时器和调度器的精度和准确性为准。线程不会失去任何监视器的所有权。

好的。现在你知道了通知只会唤醒一个同样监视这个对象的线程,但是被唤醒的线程会竞争在这个对象上同步。如果你的生产者通知消费者并释放锁,然后生产者和消费者站在同一个点上竞争。并且Thread.睡眠不做你想要的工作,它不会像文档所说的那样在睡眠时释放锁。所以这可能会发生。

总之,Thread.睡眠不太擅长同步。即使您删除了它,由于通知机制,第一个输出也会发生。

@Andrew S的回答会奏效的。