生产者 - 消费;消费者如何停止?

时间:2012-04-27 14:47:46

标签: java producer-consumer

所以我模拟了我的制作人消费者问题,我有下面的代码。我的问题是:如果消费者处于不变的状态(消极的话),消费者会如何停止。

在下面的代码中,我添加了

                    if (queue.peek()==null)
                         Thread.currentThread().interrupt();

在这个例子中很好用。但在我的真实世界设计中,这不起作用(有时生产者需要更长的时间来“放置”数据,因此消费者抛出的异常是不正确的。一般来说,我知道我可以放一个'毒'数据比如Object是XYZ,我可以在消费者中检查它。但这种毒药会使代码看起来很糟糕。想知道是否有人采用不同的方法。

public class ConsumerThread implements Runnable
{
 private BlockingQueue<Integer> queue;
 private String name;
 private boolean isFirstTimeConsuming = true;
 public ConsumerThread(String name, BlockingQueue<Integer> queue)
 {
    this.queue=queue;
    this.name=name;
 }

@Override
public void run()
{
    try
    {       
        while (true)
        {   
            if (isFirstTimeConsuming)
            {
                System.out.println(name+" is initilizing...");
                Thread.sleep(4000);
                isFirstTimeConsuming=false;
            }
            try{

                if (queue.peek()==null)
                    Thread.currentThread().interrupt();

                Integer data = queue.take();

                System.out.println(name+" consumed ------->"+data);
                Thread.sleep(70);    

            }catch(InterruptedException ie)
            {
                System.out.println("InterruptedException!!!!");
                break;
            }
        }

        System.out.println("Comsumer " + this.name + " finished its job; terminating.");

    }catch (InterruptedException e)
    {
        e.printStackTrace();
    } 
}

}

5 个答案:

答案 0 :(得分:7)

答:根本不能保证只因为peek返回null,生产者已停止生产。如果生产者放慢速度怎么办?现在,消费者退出,生产者继续生产。所以'偷看' - &gt; “破解”的想法基本上失败了。

B:如果出现以下情况,则设置来自消费者的'完成/运行'标志并在生产者中读取它也会失败:

  1. 消费者检查标志,发现它应该继续运行,然后执行'take'
  2. 同时,制作人将旗帜设置为'不跑'
  3. 现在消费者永远等待鬼包
  4. 相反的情况也可能发生,并且一个数据包被遗漏掉了。

    然后,为了解决这个问题,你需要在'BlockingQueue'之上与互斥锁进行额外的同步。

    <强> C: 在这种情况下,我发现'Rosetta Code'是决定什么是好习惯的良好来源:

    http://rosettacode.org/wiki/Synchronous_concurrency#Java

    生产者和消费者必须就表示输入结束的对象(或对象中的属性)达成一致。然后生产者在最后一个数据包中设置该属性,并且消费者停止消费它。即你在问题中提到的“毒药”。

    在上面的Rosetta Code示例中,这个'对象'只是一个空的String,称为'EOF':

    final String EOF = new String();
    
    // Producer
    while ((line = br.readLine()) != null)
      queue.put(line);
    br.close();
    // signal end of input
    queue.put(EOF);
    
    // Consumer
    while (true)
      {
        try
          {
            String line = queue.take();
            // Reference equality
            if (line == EOF)
              break;
            System.out.println(line);
            linesWrote++;
          }
        catch (InterruptedException ie)
          {
          }
      }
    

答案 1 :(得分:3)

不要在线程上使用中断,而是在不再需要时断开循环:

if (queue.peek()==null)
         break;

或者您也可以使用变量标记关闭操作挂起,然后在以下情况后断开循环并关闭循环:

if (queue.peek()==null)
         closing = true;

//Do further operations ...
if(closing)
  break;

答案 2 :(得分:0)

在现实世界中,大多数消息传递都带有某种类型的标头,用于定义消息类型/子类型或可能是不同的对象。

您可以创建一个命令和控制对象或消息类型,告诉线程在获取消息时执行某些操作(如关闭,重新加载表,添加新的侦听器等)。

这样,您可以说命令和控制线程只是将消息发送到正常的消息流中。您可以让CNC线程与大型系统中的操作终端通信等。

答案 3 :(得分:0)

如果您的队列在您希望消费者终止之前可以清空,那么您需要一个标志来告诉线程何时停止。添加setter方法,以便生产者可以告诉使用者关闭。然后修改代码,而不是:

if (queue.isEmpty())
   break;

让代码检查

if (!run)
{
   break;
}
else if (queue.isEmpty())
{
   Thread.sleep(200);
   continue;
}

答案 4 :(得分:0)

您可以将这种类型安全模式与毒药一起使用:

public sealed interface BaseMessage {

    final class ValidMessage<T> implements BaseMessage {

        @Nonnull
        private final T value;


        public ValidMessage(@Nonnull T value) {
            this.value = value;
        }

        @Nonnull
        public T getValue() {
            return value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ValidMessage<?> that = (ValidMessage<?>) o;
            return value.equals(that.value);
        }

        @Override
        public int hashCode() {
            return Objects.hash(value);
        }

        @Override
        public String toString() {
            return "ValidMessage{value=%s}".formatted(value);
        }
    }

    final class PoisonedMessage implements BaseMessage {

        public static final PoisonedMessage INSTANCE = new PoisonedMessage();


        private PoisonedMessage() {
        }

        @Override
        public String toString() {
            return "PoisonedMessage{}";
        }
    }
}

public class Producer implements Callable<Void> {

    @Nonnull
    private final BlockingQueue<BaseMessage> messages;

    Producer(@Nonnull BlockingQueue<BaseMessage> messages) {
        this.messages = messages;
    }

    @Override
    public Void call() throws Exception {
        messages.put(new BaseMessage.ValidMessage<>(1));
        messages.put(new BaseMessage.ValidMessage<>(2));
        messages.put(new BaseMessage.ValidMessage<>(3));
        messages.put(BaseMessage.PoisonedMessage.INSTANCE);
        return null;
    }
}

public class Consumer implements Callable<Void> {

    @Nonnull
    private final BlockingQueue<BaseMessage> messages;

    private final int maxPoisons;


    public Consumer(@Nonnull BlockingQueue<BaseMessage> messages, int maxPoisons) {
        this.messages = messages;
        this.maxPoisons = maxPoisons;
    }

    @Override
    public Void call() throws Exception {
        int poisonsReceived = 0;
        while (poisonsReceived < maxPoisons && !Thread.currentThread().isInterrupted()) {
            BaseMessage message = messages.take();
            if (message instanceof BaseMessage.ValidMessage<?> vm) {
                Integer value = (Integer) vm.getValue();
                System.out.println(value);
            } else if (message instanceof BaseMessage.PoisonedMessage) {
                ++poisonsReceived;
            } else {
                throw new IllegalArgumentException("Invalid BaseMessage type: " + message);
            }
        }
        return null;
    }
}