在线程间通信方面遇到麻烦,并通过在整个地方使用“虚拟消息”来“解决”它。这是一个坏主意吗?有哪些可能的解决方案?
我遇到的示例问题。
主线程启动一个线程进行处理并将记录插入数据库。 主线程读取一个可能很大的文件,并将一个记录(对象)放在一个阻塞队列中。处理线程从队列读取并确实有效。
如何告诉“处理线程”停止? 队列可以是空的但是工作没有完成,主线程现在也没有处理线程完成工作并且不能中断它。
所以处理线程
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
}
}
请注意,插入必须位于同一事务中,并且无法处理事务 通过主线程。
这样做的更好方法是什么? (我正在学习,不想开始“以错误的方式做事”)
答案 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。