如何在Java迭代器中使用ExecutorService而不会有资源泄漏的风险

时间:2012-10-18 22:46:06

标签: java concurrency executorservice resource-leak

我有一个Java迭代器,它列出了远程位置的项目。项目列表位于“页面”中,“获取下一页”操作相当慢。 (具体来说,我的迭代器称为S3Find并列出来自Amazon S3的对象。)

所以,为了加快速度,我想预取一个列表页面。为此,我使用了ExecutorServiceCallable / Future模式来预取项目的“页面”。问题是,该迭代器的调用者可能在任何时候放弃操作,而不通知我的类。例如,请考虑以下循环:

for (S3URL f : new S3Find(topdir).withRecurse(true)) {
    // do something with f
    if (some_condition) break;
}

因此,有资源泄漏,因为我用来提交ExecutorService的{​​{1}}处于活动状态并且正在运行,即使没有对包含{{1}的更多引用(即使下一个预取已经完成)。

处理此问题的正确方法是什么?我使用了错误的方法吗?我应该放弃Callable并为每个预取使用一个新的裸线程(并在预取完成后终止线程)?请注意,页面的每次提取大约需要500毫秒,因此每次创建一个新线程可能相比之下可以忽略不计。有一件事我不想是要求调用者明确告知S3Find他们已经完成了迭代(因为它肯定会被某些人遗忘)。

这是当前的预取代码(在ExecutorService内):

S3Find

2 个答案:

答案 0 :(得分:1)

这是一个经典的问题,我已经遇到过几次了。通过数据库连接发生在我身上。

  

我应该放弃ExecutorService并为每个预取使用一个新的裸线程(并在完成预取时终止线程)吗?

我猜这是你唯一的选择。我不打扰杀死线程。只是让它完成它的工作并在后台死去。为下一页分叉一个新线程。您需要加入该线程并使用某种常见的AtomicReference(或其他)来共享S3Find调用者和线程之间的结果列表。

  

我不想要的一件事是要求调用者明确告知S3Find他们已经完成迭代(因为它肯定会被某些人遗忘)。

我没有看到任何简单的方法来执行此操作“正确”没有调用方在try / finally中调用某种close()方法。难道你不能以某种方式在Javadocs中明确这一点吗?这就是我在ORMLite database iterators中所做的。

S3Find s3Find = new S3Find(topdir).withRecurse(true);
try {
    for (S3URL f : s3Find) {
        ...
    }
} finally {
    s3Find.close();
}

然后在S3Find.close()

public void close() {
    exec.shutdown();
}

在Java 7中,他们添加了try with resources construct语言自动关闭任何Closeable资源。这是一个很大的胜利。

答案 1 :(得分:0)

我想我现在有一个解决方案,虽然如上所述使用裸线程,但非常简单并且非常接近初始版本。它仍然利用了漂亮的Callable模式,但使用FutureTask代替Future,而不使用ExecutorService

之前我错过的关键是FutureTask扩展Runnable,您实际上可以通过new Thread(task)启动它。换句话说:

NextPageGetter worker = new NextPageGetter(s3, currentList);
FutureTask<ObjectListing> future = new FutureTask<>(worker);
new Thread(future).start();

然后再说:

currentList = future.get();

现在所有的资源都处理得很好,无论迭代器是否耗尽。事实上,一旦FutureTask完成,线程就会消失。

为了完整起见,这里是修改后的代码(仅class Pager已更改):

/**
 * This class holds one ObjectListing (one "page"), and also pre-fetches the next page
 * using a {@link S3Find#NextPageGetter} Callable on a separate thread.
 */
private static class Pager {
    private final AmazonS3 s3;
    private ObjectListing currentList;
    private FutureTask<ObjectListing> future;
    public Pager(AmazonS3 s3, ListObjectsRequest request) {
        this.s3 = s3;
        currentList = s3.listObjects(request);
        future = submitPrefetch();
    }
    public ObjectListing getCurrentPage() {
        return currentList;
    }
    /**
     * Move currentList to the next page, and returns it.
     */
    public ObjectListing getNextPage() {
        if (future == null) return null;
        try {
            currentList = future.get();
            future = submitPrefetch();
        } catch (InterruptedException|ExecutionException e) {
            e.printStackTrace();
        }
        return currentList;
    }
    private FutureTask<ObjectListing> submitPrefetch() {
        if (currentList == null || !currentList.isTruncated()) {
            return null;
        } else {
            NextPageGetter worker = new NextPageGetter(s3, currentList);
            FutureTask<ObjectListing> f = new FutureTask<>(worker);
            new Thread(f).start();
            return f;
        }
    }
}