Java BlockingQueue在take()上阻塞,略有不同

时间:2012-01-04 00:52:34

标签: java concurrency blockingqueue

我有一种情况,我有2个阻塞队列。第一个我插入一些我执行的任务。当每个任务完成时,它会将任务添加到第二个队列,然后执行它们。

所以我的第一个队列很简单:我只是检查以确保它不是空的并执行,否则我打断():

public void run() {
    try {
        if (taskQueue1.isEmpty()) {
            SomeTask task = taskQueue1.poll();
            doTask(task);
            taskQueue2.add(task);
        }
        else {
            Thread.currentThread().interrupt();
        }
    }

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

我执行以下操作的第二个,如您所知,不起作用:

public void run() {
    try {
        SomeTask2 task2 = taskQueue2.take();
        doTask(task2);
    }

    catch (InterruptedException ex) {

    }
    Thread.currentThread().interrupt();

}

你如何解决它,以便第二个BlockingQueue不会阻塞take(),但只有当它知道没有更多的项目要添加时才结束。如果第二个线程可能会看到第一个阻塞队列,并检查它是否为空并且第二个队列也是空的,那么它会中断。

我也可以使用Poison对象,但更喜欢别的东西。

注意:这不是确切的代码,只是我在这里写的东西:

2 个答案:

答案 0 :(得分:2)

我无法弄清楚你在这里尝试做什么,但我可以说你的第一个interrupt()方法中的run()无论是没有意义还是错误。

  • 如果您在自己的run()对象中运行Thread方法,那么该线程无论如何都要退出,所以没有必要打断它。

  • 如果您在带有线程池的执行程序中运行run()方法,那么您很可能根本不想终止该线程或关闭执行程序...此时。如果你想关闭执行程序,那么你应该调用其中一个关闭方法。


例如,这是一个版本,在没有所有中断内容的情况下,你看起来正在做什么,没有线程创建/破坏流失。

public class TaskExecutor {

    private ExecutorService executor = new ThreadPoolExecutorService(...);

    public void submitTask1(final SomeTask task) {
        executor.submit(new Runnable(){
            public void run() {
                doTask(task);
                submitTask2(task);
            }
        });
    }

    public void submitTask2(final SomeTask task) {
        executor.submit(new Runnable(){
            public void run() {
                doTask2(task);
            }
        });
    }

    public void shutdown() {
        executor.shutdown();
    }
 }

如果您想为任务单独排队,只需创建并使用两个不同的执行程序即可。

答案 1 :(得分:1)

你的声音好像处理第一个队列的线程知道一旦队列耗尽就没有其他任务就会出现。这听起来很可疑,但无论如何我会接受你的意见并提出解决方案。

定义两个线程都可见的AtomicInteger。将其初始化为正数

按如下方式定义第一个线程的操作:

  • 循环Queue#poll()
  • 如果Queue#poll()返回null,则在共享整数上调用AtomicInteger#decrementAndGet()
    • 如果AtomicInteger#decrementAndGet()返回零,则通过Thread#interrupt()中断第二个线程。 (这处理没有物品到达的情况。)
    • 在任何一种情况下,退出循环。
  • 否则,处理提取的项目,在共享整数上调用AtomicInteger#incrementAndGet(),将提取的项目添加到第二个线程的队列,然后继续循环。

定义第二个线程的操作,如下所示:

  • BlockingQueue#take()上的循环阻止。
  • 如果BlockingQueue#take()抛出InterruptedException,请捕获异常,调用Thread.currentThread().interrupt(),然后退出循环。
  • 否则,处理提取的项目。
  • 在共享整数上调用AtomicInteger#decrementAndGet()
    • 如果AtomicInteger#decrementAndGet()返回零,则退出循环。
    • 否则,继续循环。

在尝试编写实际代码之前,请确保您理解了这个想法。合同是第二个线程继续等待其队列中的更多项目,直到预期任务的计数达到零。此时,生成线程(第一个)将不再将任何新项目推送到第二个线程的队列中,因此第二个线程知道停止为其队列提供服务是安全的。

no 任务到达第一个线程的队列时,就会出现这种情况。由于第二个线程只减少并测试之后的计数它处理一个项目,如果它永远不会有机会处理任何项目,它将永远不会考虑停止。我们使用线程中断来处理这种情况,代价是第一个线程的循环终止步骤中的另一个条件分支。幸运的是,该分支只执行一次。

有许多设计可以在这里工作。我只描述了一个只引入了一个额外的实体 - 共享原子整数 - 但即便如此,它仍然是繁琐的。我认为使用毒丸会更加清晰,但我承认Queue#add()BlockingQueue#put()都不接受null作为有效元素(由于Queue#poll()的回报价值合约)。否则很容易使用null作为毒丸