我有一个Java迭代器,它列出了远程位置的项目。项目列表位于“页面”中,“获取下一页”操作相当慢。 (具体来说,我的迭代器称为S3Find
并列出来自Amazon S3的对象。)
所以,为了加快速度,我想预取一个列表页面。为此,我使用了ExecutorService
和Callable
/ 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
答案 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;
}
}
}