生产者 - 消费者跨线程通信

时间:2011-12-21 19:55:04

标签: java concurrency producer-consumer

在线程间通信方面遇到麻烦,并通过在整个地方使用“虚拟消息”来“解决”它。这是一个坏主意吗?有哪些可能的解决方案?

我遇到的示例问题。

主线程启动一个线程进行处理并将记录插入数据库。 主线程读取一个可能很大的文件,并将一个记录(对象)放在一个阻塞队列中。处理线程从队列读取并确实有效。

如何告诉“处理线程”停止? 队列可以是空的但是工作没有完成,主线程现在也没有处理线程完成工作并且不能中断它。

所以处理线程

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
    }
}

请注意,插入必须位于同一事务中,并且无法处理事务 通过主线程。

这样做的更好方法是什么? (我正在学习,不想开始“以错误的方式做事”)

3 个答案:

答案 0 :(得分:4)

这些虚拟消息有效。它被称为“毒药”。生产者发送给消费者的东西让它停止。

其他可能性是在主线程中的某处调用Thread.interrupt()并在工作线程中相应地捕获和处理InterruptedException。

答案 1 :(得分:2)

  

通过在整个地方使用“虚拟消息”来“解决”它。这是一个   馊主意?有哪些可能的解决方案?

这不是一个坏主意,它被称为“Poison Pills”,是停止基于线程的服务的合理方式。

但它只有在知道生产者和消费者的数量时才有效。

在你发布的代码中,有两个线程,一个是“主线程”,它产生数据,另一个是“处理线程”,它消耗数据,“毒丸”适用于这种情况。

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

在“主线程”中,您需要捕获InterruptedException,因为如果没有,“主线程”可能无法设置“毒药”。你可以像下面这样做,

...
try {
    // do normal processing
} catch (InterruptedException e) { /*  fall through  */  }
finally {
    MyObject dummy = new MyObject();
    dummy.setData("END");
    ...
}
...

此外,您可以尝试使用ExecutorService来解决所有问题。

(当你只需要做一些工作然后在完成所有工作时就停止工作)

void doWorks(Set<String> works, long timeout, TimeUnit unit)
    throws InterruptedException {
    ExecutorService exec = Executors.newCachedThreadPool();
    try {
        for (final String work : works)
            exec.execute(new Runnable() {
                    public void run() {
                        ...
                    }
                });
    } finally {
        exec.shutdown();
        exec.awaitTermination(timeout, unit);
    }
}
  

我正在学习,不想开始“以错误的方式做”

您可能需要阅读Book:Java Concurrency in Practice。相信我,这是最好的。

答案 2 :(得分:0)

你能做什么(我在最近的一个项目中做过)是包装队列然后添加'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 (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();
}

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