实践中的Java并发:BoundedExecutor中的竞争条件?

时间:2012-04-10 18:04:09

标签: java concurrency race-condition executor

实践中的Java Concurrency一书中BoundedExecutor的实现有些奇怪。

当Executor中有足够的线程排队或运行时,它应该通过阻止提交线程来限制任务提交给Executor。

这是实现(在catch子句中添加缺少的rethrow之后):

public class BoundedExecutor {
    private final Executor exec;
    private final Semaphore semaphore;

    public BoundedExecutor(Executor exec, int bound) {
        this.exec = exec;
        this.semaphore = new Semaphore(bound);
    }

    public void submitTask(final Runnable command) throws InterruptedException, RejectedExecutionException {
        semaphore.acquire();

        try {
            exec.execute(new Runnable() {
                @Override public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            semaphore.release();
            throw e;
        }
    }

当我使用BoundedExecutor和4的边界实例化Executors.newCachedThreadPool()时,我希望缓存的线程池实例化的线程数永远不会超过4.但实际上,它确实。我已经得到了这个小测试程序来创建多达11个线程:

public static void main(String[] args) throws Exception {
    class CountingThreadFactory implements ThreadFactory {
        int count;

        @Override public Thread newThread(Runnable r) {
            ++count;
            return new Thread(r);
        }           
    }

    List<Integer> counts = new ArrayList<Integer>();

    for (int n = 0; n < 100; ++n) {
        CountingThreadFactory countingThreadFactory = new CountingThreadFactory();
        ExecutorService exec = Executors.newCachedThreadPool(countingThreadFactory);

        try {
            BoundedExecutor be = new BoundedExecutor(exec, 4);

            for (int i = 0; i < 20000; ++i) {
                be.submitTask(new Runnable() {
                    @Override public void run() {}
                });
            }
        } finally {
            exec.shutdown();
        }

        counts.add(countingThreadFactory.count);
    }

    System.out.println(Collections.max(counts));
}

我认为在信号量发布和任务结束之间有一个小小的时间框架,其中另一个线程可以获取许可并在发布线程尚未完成时提交任务。换句话说,它有竞争条件。

有人可以证实吗?

3 个答案:

答案 0 :(得分:10)

BoundedExecutor确实是为了说明如何限制任务提交,而不是作为一种限制线程池大小的方法。至少有一条评论指出,有更多直接的方法来实现后者。

但是其他答案没有提到书中使用无界队列的文字和

  

将信号量上的边界设置为等于池大小加上   您希望允许的排队任务数,因为信号量是   限制当前正在执行和等待的任务数量   执行。 [JCiP,8.3.3节结束]

通过提及无界队列和池大小,我们暗示(显然不是很清楚)使用有界大小的线程池。

然而,一直困扰我的BoundedExecutor是它没有实现ExecutorService接口。实现类似功能并仍然实现标准接口的现代方法是使用Guava的listeningDecorator方法和ForwardingListeningExecutorService类。

答案 1 :(得分:5)

你对比赛条件的分析是正确的。 ExecutorService&amp ;;之间没有同步保证。信号量。

但是,我不知道是否限制线程数是BoundedExecutor的用途。我认为更多的是限制提交给服务的任务数量。想象一下,如果你有500万个需要提交的任务,如果你提交了超过10,000个任务,你的内存就会耗尽。

那么在任何给定时间你只会运行4个线程,你为什么要尝试排队所有5百万个任务?您可以使用与此类似的构造来限制在任何给定时间排队的任务数。您应该得到的是,在任何给定时间,只有4个任务在运行。

显然,对此的解决方法是使用Executors.newFixedThreadPool(4)

答案 2 :(得分:2)

我看到一次创建多达9个线程。我怀疑存在竞争条件导致线程多于所需的线程。

这可能是因为在运行任务工作之前和之后都要完成。这意味着即使代码块中只有4个线程,也有一些线程会停止前一个任务或准备开始一个新任务。

即。线程在它仍在运行时执行release()。即使你做的最后一件事不是它在获得新任务之前做的最后一件事。