Java 生产者-消费者线程间通信

Java 生产者-消费者线程间通信,java,concurrency,producer-consumer,Java,Concurrency,Producer Consumer,线程间通信有问题,并通过到处使用“虚拟消息”来“解决”。这是个坏主意吗?可能的解决办法是什么 我有一个例子问题 主线程启动一个线程,用于处理记录并将其插入数据库。 主线程读取一个可能很大的文件,并将一个记录(对象)一个接一个地放入blockingqueue。处理线程从队列中读取数据并执行操作 如何让“处理线程”停止? 队列可以为空,但工作未完成,并且当处理线程完成工作并且不能中断它时,主线程现在也不会 处理线程就是这样 while (queue.size() > 0 || !Thread.

线程间通信有问题,并通过到处使用“虚拟消息”来“解决”。这是个坏主意吗?可能的解决办法是什么

我有一个例子问题

主线程启动一个线程,用于处理记录并将其插入数据库。 主线程读取一个可能很大的文件,并将一个记录(对象)一个接一个地放入blockingqueue。处理线程从队列中读取数据并执行操作

如何让“处理线程”停止? 队列可以为空,但工作未完成,并且当处理线程完成工作并且不能中断它时,主线程现在也不会

处理线程就是这样

while (queue.size() > 0 || !Thread.currentThread().isInterrupted()) {
    MyObject object= queue.poll(100, TimeUnit.MILLISECONDS);
    if (object != null) {
        String data = object.getData();
        if (data.equals("END")) {
            break;
        }
    // do work
    }
}
// clean-up
synchronized queue) {
    queue.notifyAll();
}
return;
和主线

// ...start processing thread...
while(reader.hasNext(){
    // ...read whole file and put data in queue...
}
MyObject dummy = new MyObject();
dummy.setData("END");
queue.put(dummy);
//Note: empty queue here means work is done
while (queue.size() > 0) {
    synchronized (queue) {
        queue.wait(500); // over-cautios locking prevention i guess
    }
}
请注意,插入必须在同一事务中,并且不能处理该事务 通过主线程

有什么更好的方法可以做到这一点?
(我正在学习,不想开始“以错误的方式做”)

这些伪消息是有效的。它被称为“毒药”。生产者发送给消费者使其停止的东西

另一种可能是在主线程的某个位置调用Thread.interrupt(),并在工作线程中相应地捕获和处理InterruptedException

通过在所有地方使用“虚拟消息”来“解决”它。这是一辆汽车吗 坏主意?可能的解决办法是什么

这不是一个坏主意,它被称为“毒丸”,是阻止基于线程的服务的合理方式

但它只有在知道生产者和消费者的数量时才起作用

在您发布的代码中,有两个线程,一个是“主线程”,它生成数据,另一个是“处理线程”,它消耗数据,“毒丸”在这种情况下运行良好

但想象一下,如果你还有其他生产商,消费者如何知道何时停止(只有在所有生产商都发送“毒丸”时),你需要知道所有生产商的确切数量,并检查消费者中的“毒丸”数量,如果它等于生产商的数量,这意味着所有生产商都停止工作,然后消费者停止

在“主线程”中,您需要捕获
中断异常
,否则,“主线程”可能无法设置“毒丸”。你可以像下面这样做

...
try {
    // do normal processing
} catch (InterruptedException e) { /*  fall through  */  }
finally {
    MyObject dummy = new MyObject();
    dummy.setData("END");
    ...
}
...
此外,您还可以尝试使用执行器服务解决所有问题

(当您只需要做一些工作,然后在完成所有工作后停止时,它就可以工作)

void doWorks(设置工作、长超时、时间单位)
抛出中断异常{
ExecutorService exec=Executors.newCachedThreadPool();
试一试{
用于(最终管柱工程:工程)
exec.execute(新的Runnable(){
公开募捐{
...
}
});
}最后{
exec.shutdown();
执行等待终止(超时,单位);
}
}
我正在学习,不想开始“做错事”

您可能需要阅读这本书:实践中的Java并发。相信我,这是最好的。

您可以做的(我在最近的一个项目中做过)是包装队列,然后添加一个
'isOpen()'
方法

class ClosableQ<T> {

   boolean isOpen = true;

   private LinkedBlockingQueue<T> lbq = new LinkedBlockingQueue<T>();

   public void put(T someObject) {
      if (isOpen) {
         lbq.put(someObject);
      }
   }

   public T get() {
      if (isOpen) {
         return lbq.get(0);
      }
   }

   public boolean isOpen() {
      return isOpen;
   }

   public void open() {
      isOpen = true;
   }

   public void close() {
      isOpen = false;
   }
}
读者线程:

while (dataQ.isOpen) {
   someObject = dataQ.get();
}

当然,您可以扩展列表,但这会为用户提供您可能不想要的访问级别。您需要在代码中添加一些并发性内容,比如AtomicBoolean。

Ok。如果传递的对象类型没有一个字符串字段可以轻松用于此操作,我将如何使用此字段?如果任何文本都可以是有效数据,因此可以等同于毒药呢?使用GUID?理想情况下,您应该更改MyObject类并在其中添加方法isPoison()。此方法如何确定对象是否为毒药取决于您,可能是比较消息是否为null、空或消息是否显式创建为毒药之类的简单事情。一个想法是将MyObject重构为一个具有两个实现的接口,一个MessageObject在isPoison()方法中总是返回false,另一个ToxinMessage总是返回true。但这在很大程度上取决于您具体在做什么,考虑到您的实际情况,可能存在更好的解决方案。我知道executor服务,但数据读取、处理(验证)和插入不能在同一个任务或方法中,因为它们都应在同一个数据库事务中运行。如果你明白我的意思。好吧,我明白,
ExecutorService
只是我的一般建议,根据你的要求使用或不使用它。但是要小心捕获所有异常,如果某些异常未捕获,您可能会失去设置“毒丸”的机会。我正在将队列传递给一个方法。读了你的想法,我有以下几点。该方法还可以接受原子布尔keepProcessing。然后只要queue.size>0 | | keepProcessing.get()它就可以运行
while (reader.hasNext() ) {
  // read the file and put it into the queue
  dataQ.put(someObject);
} 
// now we're done
dataQ.close();
while (dataQ.isOpen) {
   someObject = dataQ.get();
}