如何在Java迭代器中使用ExecutorService而不冒资源泄漏的风险
我有一个Java迭代器,它列出来自远程位置的项。项目列表以“页面”形式出现,“获取下一页”操作相当缓慢。(具体来说,我的迭代器称为如何在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
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;
}
}
}