我遇到了一个Java多线程文件爬虫问题。我的问题是我有一个workQueue,它是一个linkedBlockingQueue,它包含我想用我的线程抓取的文件的名称,每个线程将->
来自workQueue,并且在扫描文件时它可能take()
另一个文件名放入workQueue(它是一个依赖检查器程序)。因此,当我们完成所有工作并且所有线程最终都会从(最终)空的workQueue中尝试put()
时,所有线程最终都会进入等待状态时,我永远不会确定。
所以我想我的问题是,一旦完成所有工作(当所有线程都进入等待状态时)是否有一种终止所有线程的有效方法?目前我只在主线程上使用take()
,然后在所有工作线程中使用sleep()
。
对不起,如果这个问题听起来很混乱。
答案 0 :(得分:1)
之前我遇到过这个问题,我发现的唯一方法就是向BlockingQueue
发送一个特殊的标记对象。当队列.take()
对象时,如果这是标记,则Thread
会自行结束。
我尝试过其他解决方案,比如唤醒线程并检测异常,但没有成功。
答案 1 :(得分:1)
有一种称为Poison Pill的模式对此有好处。基本上,当生成器完成后,在队列中插入一个特殊值,告诉消费者停止。您可以为每个消费者插入一个药丸,或者,一旦消费者获得毒丸,将其返回到下一个消费者的队列中。因为听起来你只是在将字符串排列,比如
public static final String POISON_PILL = "DONE";
或者在Java 8中,使用Optional
来包装你的值,然后就不会出现问题。
BlockingQueue<Optional<...>> queue;
另一种选择是使用ExecutorService
(实际上由BlockingQueue
支持)并将每个文件作为自己的任务提交,然后在完成后使用executorService.shutdown()
。这样做的问题在于它比你需要的代码更紧密地耦合代码,这使得重用资源(如数据库和HTTP连接)变得更加困难。
我会避免打断你的工作人员发出信号,因为这会导致阻止IO操作失败。
答案 2 :(得分:0)
您可以使用下面的approch。如果需要,添加观察者模式。 或者简单地 - 不是使用死亡数据包发信号,而是收集等待线程的列表,然后中断()它们。
public class AccessCountingLinkedPrioQueue<T> {
private final LinkedBlockingQueue<T> mWrappingQueue = new LinkedBlockingQueue<>();
private final Object mSyncLockObj = new Object();
private final int mMaxBlockingThreads;
private final T mDeathSignallingObject;
private volatile int mNumberOfThreadsInAccessLoop = 0;
public AccessCountingLinkedPrioQueue(final int pMaxBlockingThreads, final T pDeathSignallingObject) {
mMaxBlockingThreads = pMaxBlockingThreads;
mDeathSignallingObject = pDeathSignallingObject;
}
public T take() throws InterruptedException {
final T retVal;
synchronized (mSyncLockObj) {
++mNumberOfThreadsInAccessLoop;
}
synchronized (mWrappingQueue) {
if (mNumberOfThreadsInAccessLoop >= mMaxBlockingThreads && mWrappingQueue.isEmpty()) signalDeath();
retVal = mWrappingQueue.take();
}
synchronized (mSyncLockObj) {
--mNumberOfThreadsInAccessLoop;
}
return retVal;
}
private void signalDeath() {
for (int i = 0; i < mMaxBlockingThreads; i++) {
mWrappingQueue.add(mDeathSignallingObject);
}
}
public int getNumberOfThreadsInAccessLoop() {
return mNumberOfThreadsInAccessLoop;
}
}
class WorkPacket {
// ... your content here
}
class MultiThreadingBoss {
static public final WorkPacket DEATH_FROM_ABOVE = new WorkPacket();
public MultiThreadingBoss() {
final int THREADS = 7;
final AccessCountingLinkedPrioQueue<WorkPacket> prioQ = new AccessCountingLinkedPrioQueue<>(THREADS, DEATH_FROM_ABOVE);
for (int i = 0; i < THREADS; i++) {
final ThreadedWorker w = new ThreadedWorker(prioQ);
new Thread(w).start();
}
}
}
class ThreadedWorker implements Runnable {
private final AccessCountingLinkedPrioQueue<WorkPacket> mPrioQ;
public ThreadedWorker(final AccessCountingLinkedPrioQueue<WorkPacket> pPrioQ) {
mPrioQ = pPrioQ;
}
@Override public void run() {
while (true) {
try {
final WorkPacket p = mPrioQ.take();
if (p == MultiThreadingBoss.DEATH_FROM_ABOVE) break; // or return
// ... do your normal work here
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
}
}