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

The*_*ist 4 java concurrency java.util.concurrent

你能发现这个bug吗?这将抛出一个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) {
                    }
                }
            });
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

错误是我调用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

任何想法将不胜感激!

编辑:ThreadPoolExecutor小号purge()看起来前途无量,但它只能清洗取消的任务.

eri*_*son 8

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);
  }
Run Code Online (Sandbox Code Playgroud)

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


Joh*_*int 7

并不是返回的Futures就是你的问题.问题是,对于您提交的每个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% 
------------------------------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

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

  • 你也可以直接创建一个新的ThreadPoolExecutor,传入你自己的BlockingQueue(例如一个新的LinkedBlockingQueue(1000)),用最大容量构造 (4认同)
  • @The Alchemist这是问题所在.在内存中拥有2000个Runnables不是问题,但是您的示例代码将在几秒钟内创建数百万个可运行的并且不会立即执行,并且这会导致OOM - 您必须设置繁忙的循环提交任务.(一旦任务执行完毕,它们将可用于垃圾收集,但在它们执行之前,它们会留在内存中) (3认同)
  • @The Alchemist:一旦任务运行完毕,"Future"将有资格进行垃圾收集,所以不会永远不会在内存中存在. (3认同)
  • @The Alchemist,你是对的.只要您提交FutureTask,它们将永远留在内存中(或者工作线程将它们从队列中挑出并处理它们).那么问题是你不应该在给定时间将这么多Runnables提交到ExecutorService.除非将它们存储在内存中,否则没有其他方法可以让服务了解Runnables (2认同)
  • @The Alchemist作为我最近的编辑笔记 - 如果未来的处理速度足够快,你将永远不会遇到内存问题.也就是说,一个线程将Future从队列中拉出,调用其run方法并返回.这将摆脱未来,它将不再是在记忆中. (2认同)
  • 这就是我从一开始就说的:) (2认同)