как вы "игнорируете" java.util.concurrent.Future объекты? - PullRequest
4 голосов
/ 29 апреля 2011

Можете ли вы обнаружить ошибку?Это выдаст 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);
    }
}

Что делать с задачами, которые ничего не возвращают, а только вычисляют?

Буду признателен за любые идеи!

РЕДАКТИРОВАТЬ : ThreadPoolExecutors purge() выглядел многообещающе, но он только удаляет отмененные задачи.

Ответы [ 2 ]

8 голосов
/ 29 апреля 2011

На возвращаемый объект 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, а в том, что задачи ставятся в очередь со скоростью, с которой они могут быть обработаны.

7 голосов
/ 29 апреля 2011

Это не столько Фьючерсы, которые возвращаются, это ваша проблема. Проблема заключается в том, что для каждого представляемого Runnable ExecutorService будет хранить их для последующей обработки. Каждый раз, когда вы вызываете метод submit с помощью Runnable (или Future), ExecutorService отправляет этот runnable в рабочую очередь. Runnable будет сидеть там до тех пор, пока Поток не сможет выбрать этот Runnable из очереди (позднее). Если все рабочие потоки заняты, то ExecutorService просто поместит исполняемый файл в указанную очередь.

Итак, ваша проблема в том, что у вас есть только один поток, пытающийся получить очередь, которая бесконечно добавляется другим потоком. Он добавляется гораздо быстрее, чем рабочий поток может обрабатывать каждый Runnable.

Редактировать: Пример кода, который я привел в качестве исключений, на самом деле генерирует исключение RejectedExecutionException, поэтому механизм регулирования должен был бы немного отличаться, если бы вы выбрали.

Что касается лучшего решения, как я упоминал в своем комментарии; Если вы ожидаете заполнить ExecutorService таким образом, чтобы рабочие потоки не могли справиться с очередью, вы можете сериализовать и десериализовать запросы по мере их поступления (создавая свой собственный ThreadPoolExecutor), но я убедился бы в необходимости такого дело абсолютно необходимо.

Имейте в виду, что после того, как работа будет сделана, будущее будет сброшено, а мусор будет собран. Так что если вы делаете одно Future в секунду, и оно выполняется менее чем за секунду, само Future будет удалено, и у вас не будет проблем с памятью. Но если вы делаете одно Future в секунду, а потоки делают Future каждые 3 секунды, это будет рисовать и выдавать.

Редактировать: Я профилировал кучу программы, которую вы запускаете, и проблема именно в этом. FutureTask, создаваемый ExecutorService, находится в рабочей очереди, пока рабочий поток не выберет его

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, которые вы можете себе представить

...