你如何“忽略”java.util.concurrent.Future对象?

时间:2011-04-28 21:31:51

标签: java concurrency java.util.concurrent

你能发现这个错误吗?这将抛出java.lang.OutOfMemoryError

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestTheads {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(1);
        while(true) {
            executorService.submit(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }
                }
            });
        }
    }

}

错误是我调用了executorService.submit()而不是executorService.execute(),因为submit()会返回一个我忽略的Future对象。使用execute(),此程序实际上将永远运行。

但是,使用execute()方法并不总是很有用,就像使用ScheduledExecutorService时一样:

public static void main(String[] args) {
    // this will FAIL because I ignore the ScheduledFuture object
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
    while(true) {
        executorService.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                }
            }
        }, 1, 1, TimeUnit.SECONDS);
    }
}

对于不返回任何内容的任务应该做什么,只计算?

任何想法都将不胜感激!

编辑ThreadPoolExecutor purge()看起来很有前途,但它只清除已取消的任务。

2 个答案:

答案 0 :(得分:8)

Future 强烈引用返回的ExecutorService对象,直到它被执行。(它实际上是委托给FutureTask的{​​{1}}实例你的Runnable。)一旦执行,它将被垃圾收集,因为调用者没有引用它。换句话说,记忆问题与Future的处理无关。

如果内存不足,那是因为工作队列有数百万个排队的任务。与任何队列一样,除非平均消耗率超过平均生产率,否则队列将填满。队列的内容消耗内存。

使用有界队列,这将有效地限制任务排队,或获得更多内存。

此代码将“永久”运行:

  ExecutorService executorService = Executors.newFixedThreadPool(1);
  while(true) {
    executorService.submit(new Runnable() {
      public void run() {
        try {
          Thread.sleep(10);
        } catch (InterruptedException e) { }
      }
    });
    Thread.sleep(12);
  }

差异不在于处理生成的Future实例,而是以可以处理它们的速率对任务进行排队。

答案 1 :(得分:7)

回归的未来不是你的问题。问题是,对于您提交的每个Runnable,ExecutorService将存储它们以便以后能够处理。每次使用Runnable(或Future)调用submit方法时,ExecutorService都会将该runnable推送到工作队列。 Runnable将坐在那里,直到一个Thread可以从队列中选择Runnable(稍后的时间)。如果所有工作线程都忙,那么ExecutorService将把runnable简单地放到所述队列中。

所以你的问题是你只有一个线程试图拉出另一个线程无限增加的队列。它的添加速度要快得多,工作线程可以处理每个Runnable。

编辑:我给出的代码示例实际上抛出了RejectedExecutionException,因此如果您选择,限制机制必须略有不同。

就像我在评论中提到的更好的解决方案而言;如果您希望以工作线程无法跟上队列的方式填充ExecutorService,您可以在请求进入时对其进行序列化和反序列化(构建您自己的ThreadPoolExecutor),但我会确保需要这样的案件是绝对必要的。

在完成工作后请记住,Future将受到谴责并收集垃圾。因此,如果您每秒执行一次Future,并且它会在一秒钟内执行,则Future本身将被删除,您将不会遇到内存问题。但是,如果你每隔一秒做一个Future,并且线程每3秒做一次Future,那将会绘制并发布。

编辑:我描述了您正在运行的程序的堆,问题正是如此。由ExecutorService创建的FutureTask位于工作队列中,直到工作线程将其关闭为止

Class Name                                                       | Shallow Heap | Retained Heap | Percentage 
------------------------------------------------------------------------------------------------------------
java.util.concurrent.ThreadPoolExecutor @ 0x78513c5a0            |          104 | 2,051,298,872 |     99.99% 
|- java.util.concurrent.LinkedBlockingQueue @ 0x785140598        |           80 | 2,051,298,216 |     99.99% 
|  |- java.util.concurrent.LinkedBlockingQueue$Node @ 0x785142dd8|           32 | 2,051,297,696 |     99.99% 
------------------------------------------------------------------------------------------------------------

堆分析还有一点,你可以想象有很多LinkedBlockingQueue $ Node