Подтвердить что ты не робот

New Thread (задача).start() VS ThreadPoolExecutor.submit(задача) в Android

В моем проекте Android у меня было много мест, где мне нужно было запустить некоторый код асинхронно (веб-запрос, вызов db и т.д.). Это не долго выполняемые задачи (максимум несколько секунд). До сих пор я делал такие вещи с созданием нового потока, передавая ему новую runnable с задачей. Но недавно я прочитал статью о потоках и concurrency в Java и понял, что создание нового потока для каждой отдельной задачи не является хорошим решением.

Итак, теперь я создал ThreadPoolExecutor в моем классе Application, который содержит 5 потоков. Вот код:

public class App extends Application {

    private ThreadPoolExecutor mPool;

    @Override
    public void onCreate() {
        super.onCreate();

        mPool =  (ThreadPoolExecutor)Executors.newFixedThreadPool(5);
    }
}

А также у меня есть метод отправки Runnable-задач исполнителю:

public void submitRunnableTask(Runnable task){
    if(!mPool.isShutdown() && mPool.getActiveCount() != mPool.getMaximumPoolSize()){
        mPool.submit(task);
    } else {
        new Thread(task).start();
    }
}

Поэтому, когда я хочу запустить асинхронную задачу в своем коде, я получаю экземпляр App и вызываю метод submitRunnableTask, передавая ему runnable. Как вы можете видеть, я также проверяю, имеет ли пул потоков бесплатные потоки для выполнения моей задачи, если нет, я создаю новый поток (я не думаю, что это произойдет, но в любом случае... я не знаю, t хочу, чтобы моя задача ждать в очереди и замедлять приложение).

В методе обратного вызова onTerminate приложения я я завершаю пул.

Итак, мой вопрос следующий: Является ли этот тип шаблонов лучше, чем создание новых потоков в коде? Какие плюсы и минусы имеет мой новый подход? Может ли это вызвать проблемы, которые я еще не знаю? Можете ли вы посоветовать мне что-то лучше этого для управления моими асинхронными задачами?

P.S. У меня есть некоторый опыт работы на Android и Java, но я далек от того, чтобы быть гуру concurrency). Возможно, есть аспекты, которые я плохо понимаю в таких вопросах. Любые советы будут оценены.

4b9b3361

Ответ 1

Этот ответ предполагает, что ваши задачи короткие

Является ли этот вид шаблона лучше, чем создание новых потоков в коде?

Это лучше, но это еще далеко от идеала. Вы по-прежнему создаете потоки для выполнения коротких задач. Вместо этого вам просто нужно создать другой пул потоков - например, Executors.newScheduledThreadPool(int corePoolSize).

Какая разница в поведении?

  • A FixedThreadPool всегда будет иметь набор потоков для использования, и если все потоки заняты, новая задача будет помещена в очередь.
  • A (по умолчанию) ScheduledThreadPool, созданный классом Executors, имеет минимальный пул потоков, который он сохраняет, даже когда он работает. Если все потоки заняты, когда приходит новая задача, она создает для него новый поток и удаляет поток через 60 секунд после его завершения, если только это не понадобится снова.

Второй может позволить вам не создавать новые потоки самостоятельно. Такое поведение может быть достигнуто без части "Запланированная", но вам придется самостоятельно построить исполнителя. Конструктор

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)

Различные параметры позволяют вам точно настраивать поведение.

Если некоторые задачи длинны...

И я имею в виду долго. Как и в большинстве ваших жизненных циклов приложения (в реальном времени 2-стороннее соединение? Порт сервера? Многоадресный прослушиватель?). В этом случае приведение вашего Runnable в исполнителя является вредным - стандартные исполнители не предназначены для того, чтобы справиться с ним, и их производительность будет ухудшаться.

Подумайте о своем фиксированном пуле потоков - если у вас есть 5 длительных задач, тогда любая новая задача породит новый поток, полностью разрушая любые возможные выгоды пула. Если вы используете более гибкий исполнитель, некоторые потоки будут использоваться совместно, но не всегда.

Правило большого пальца

  • Если это короткая задача - используйте исполнителя.
  • Если это долгая задача - убедитесь, что ваш исполнитель может ее обработать (т.е. он либо не имеет максимального размера пула, либо достаточно макс потоков для обработки еще одного потока на некоторое время)
  • Если это параллельный процесс, который должен всегда запускаться вместе с основным потоком, используйте другой поток.

Ответ 2

Чтобы ответить на ваш вопрос - Да, использование Executor лучше, чем создание новых потоков, потому что:

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

Ответ 3

Основываясь на комментарии Ordous, я изменил свой код для работы только с одним пулом.

public class App extends Application {

    private ThreadPoolExecutor mPool;

    @Override
    public void onCreate() {
        super.onCreate();

        mPool =  new ThreadPoolExecutor(5, Integer.MAX_VALUE, 1, TimeUnit.MINUTES, new SynchronousQueue<Runnable>());
    }
}


public void submitRunnableTask(Runnable task){
    if(!mPool.isShutdown() && mPool.getActiveCount() != mPool.getMaximumPoolSize()){
        mPool.submit(task);
    } else {
        new Thread(task).start(); // Actually this should never happen, just in case...
    }
}

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