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

如何在Java迭代器中使用ExecutorService而不冒资源泄漏的风险,java,concurrency,executorservice,resource-leak,Java,Concurrency,Executorservice,Resource Leak,我有一个Java迭代器,它列出来自远程位置的项。项目列表以“页面”形式出现,“获取下一页”操作相当缓慢。(具体来说,我的迭代器称为S3Find,列出了AmazonS3中的对象) 所以,为了加快速度,我想预取一个列表页面。为此,我使用了ExecutorService和Callable/Future模式来预取项目的“页面”。问题是,迭代器的调用方可能随时放弃该操作,而不通知我的类。例如,考虑下面的循环: for (S3URL f : new S3Find(topdir).withRecurse(tr

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

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

for (S3URL f : new S3Find(topdir).withRecurse(true)) {
    // do something with f
    if (some_condition) break;
}
因此,a会发生资源泄漏,因为我用来提交可调用的
ExecutorService
保持活动状态并运行,即使不再引用包含的
S3Find
(并且即使下一次预取已完成)

正确的处理方法是什么?我是否使用了错误的方法?我是否应该放弃ExecutorService,在每次预取时使用一个新的裸线程(并在预取完成时终止该线程)?请注意,每次获取一个页面大约需要500毫秒,因此每次创建一个新线程相比之下可能可以忽略不计。我不想的一件事是要求调用方显式地通知
S3Find
他们已经完成了迭代(这肯定会被一些人忘记)

这是当前的预取代码(在
S3Find
中):

/**
*此类包含一个ObjectListing(一个“页面”),还包含预取
*下一页使用{@link S3Find#NextPageGetter}可在
*分开的线。
*/
专用静态类寻呼机{
私人决赛AmazonS3 s3;
私有对象列表;
私人未来;
私人最终执行人;
公共寻呼机(AmazonS3 s3,ListObjectsRequest请求){
这1.s3=s3;
currentList=s3.listObjects(请求);
exec=Executors.newSingleThreadExecutor();
future=submitPrefetch();
}
公共对象列表getCurrentPage(){
返回当前列表;
}
/**
*将currentList移到下一页并返回它。
*/
public ObjectListing getNextPage(){
if(future==null)返回null;
试一试{
currentList=future.get();
future=submitPrefetch();
}捕获(中断异常|执行异常e){
e、 printStackTrace();
}
返回当前列表;
}
私人未来提交预览(){
if(currentList==null | |!currentList.isTruncated()){
exec.shutdown();
返回null;
}否则{
NextPageGetter worker=新的NextPageGetter(s3,currentList);
返回执行提交(工人);
}
}
}
/**
*此类检索被截断的ObjectList的“下一页”。
*它应该以可调用/未来模式调用。
*/
私有静态类NextPageGetter实现可调用{
私人最终目标清单;
私人决赛AmazonS3 s3;
public NextPageGetter(AmazonS3 s3,对象列表currentList){
超级();
这1.s3=s3;
this.currentList=currentList;
if(currentList==null | |!currentList.isTruncated()){
抛出新的IllegalArgumentException(currentList==null?
“空列表”:“列表未被截断”);
}
}
@凌驾
public ObjectListing call()引发异常{
ObjectListing nextList=s3.listNextBatchOfObjects(currentList);
返回下一个列表;
}
}

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

我是否应该放弃ExecutorService,在每次预取时使用一个新的裸线程(并在预取完成时终止该线程)

我想那是你唯一的选择。我不会费心去杀线的。只要让它完成任务,死在幕后。为下一页添加新的线程。您需要加入线程,并使用某种常见的
AtomicReference
(或其他)在
S3Find
调用程序和线程之间共享结果列表

我不想要求调用方显式地通知S3Find他们已经完成了迭代(因为某些人肯定会忘记这一点)

如果调用方不在try/finally中调用某种
close()
方法,我看不到任何简单的“正确”方法。在Javadocs中,您不能明确地说明这一点吗?这就是我在工作中所做的

然后在
S3Find.close()中:


在Java7中,他们添加了自动关闭任何
Closeable
资源的选项。这是一个巨大的胜利。

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

我是否应该放弃ExecutorService,在每次预取时使用一个新的裸线程(并在预取完成时终止该线程)

我想那是你唯一的选择。我不会费心去杀线的。只要让它完成任务,死在幕后。为下一页添加新的线程。您需要加入线程,并使用某种常见的
AtomicReference
(或其他)在
S3Find
调用程序和线程之间共享结果列表

我不想要求调用方显式地通知S3Find他们已经完成了迭代(因为某些人肯定会忘记这一点)

如果调用方不在try/finally中调用某种
close()
方法,我看不到任何简单的“正确”方法。在Javadocs中,您不能明确地说明这一点吗?这就是我在工作中所做的

然后在
S3Find.close()中:

在Java7中,他们添加了自动关闭
/**
 * 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 Future<ObjectListing> future;
    private final ExecutorService exec;
    public Pager(AmazonS3 s3, ListObjectsRequest request) {
        this.s3 = s3;
        currentList = s3.listObjects(request);
        exec = Executors.newSingleThreadExecutor();
        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 Future<ObjectListing> submitPrefetch() {
        if (currentList == null || !currentList.isTruncated()) {
            exec.shutdown();
            return null;
        } else {
            NextPageGetter worker = new NextPageGetter(s3, currentList);
            return exec.submit(worker);
        }
    }
}

/**
 * This class retrieves the "next page" of a truncated ObjectListing.
 * It is meant to be called in a Callable/Future pattern.
 */
private static class NextPageGetter implements Callable<ObjectListing> {
    private final ObjectListing currentList;
    private final AmazonS3 s3;

    public NextPageGetter(AmazonS3 s3, ObjectListing currentList) {
        super();
        this.s3 = s3;
        this.currentList = currentList;
        if (currentList == null || !currentList.isTruncated()) {
            throw new IllegalArgumentException(currentList==null ?
                        "null List" : "List is not truncated");
        }
    }

    @Override
    public ObjectListing call() throws Exception {
        ObjectListing nextList = s3.listNextBatchOfObjects(currentList);
        return nextList;
    }
}
S3Find s3Find = new S3Find(topdir).withRecurse(true);
try {
    for (S3URL f : s3Find) {
        ...
    }
} finally {
    s3Find.close();
}
public void close() {
    exec.shutdown();
}
NextPageGetter worker = new NextPageGetter(s3, currentList);
FutureTask<ObjectListing> future = new FutureTask<>(worker);
new Thread(future).start();
currentList = future.get();
/**
 * 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;
        }
    }
}