5

你能发现错误吗?这将抛出一个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()方法,比如使用 a 时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()

4

2 回答 2

9

返回的Future对象在ExecutorService 执行之前被唯一的强引用。(它实际上是一个FutureTask委托给你的实例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实例的处理,而是任务以​​可以处理的速率排队。

于 2011-04-28T22:29:46.210 回答
7

退回的期货并不是您的问题。问题是,对于您提交的每个 Runnable,ExecutorService 将存储它们以供以后处理。每次您使用 Runnable(或 Future)调用 submit 方法时,ExecutorService 都会将该 runnable 推送到工作队列。Runnable 将坐在那里,直到线程可以从队列中选择该 Runnable(稍后)。如果所有工作线程都忙,那么 ExecutorService 将简单地将可运行对象放入所述队列。

所以你的问题是你只有一个线程试图拉出一个被另一个线程无限添加的队列。它的添加速度要快得多,然后工作线程可以处理每个 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

于 2011-04-28T21:51:14.797 回答