ThreadPoolExecutor пул нитей - 1

— Рядовой программист рано или поздно сталкивается с тем, что у него есть много маленьких задач, которые нужно выполнять время от времени.

Если ты пишешь игру, то это действия, которые выполняют отдельные персонажи.

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

Все большие задачи рано или поздно разбиваются на набор маленьких и удобных задач.

Вот так на этом фоне незаметно и возникает вопрос – а как ими всеми управлять? Если в минуту нужно выполнить несколько сотен задач? Создавать для каждой задачи свою нить бывает не очень рационально. Для каждой нити Java-машина выделяет довольно много ресурсов.

Другими словами – создание и уничтожение отработавшей нити может тратить больше ресурсов и времени, чем само выполняемое задание.

Java-разработчики придумали элегантное решение этой проблемы — ThreadPoolExecutor.

ThreadPoolExecutor – это класс, который имеет внутри две вещи:

А) Очередь задач, в которую можно добавлять задачи, по мере их появления в программе.

Б) Пул-нитей (группа нитей) – которые эти задачи исполняют.

При этом нити не уничтожаются после выполнения задания, а засыпают. Чтобы начать выполнять новое задание, как только оно появится.

При создании ThreadPoolExecutor, можно задать максимальное количество нитей, которые будут созданы и максимальное количество заданий, которые можно поместить в очередь. Т.е. можно ограничить количество нитей числом 10, например, а количество задач в очереди – 100.

Как работает ThreadPoolExecutor:

1) При добавлении нового задания, оно помещается в конец очереди.

2) Если очередь заполнена будет выкинуто исключение.

3) Каждая нить после выполнения задания берет очередное задание из очереди и начинает выполнять его.

4) Если задач в очереди нет, нить засыпает до их добавления.

Подход с ограничением количества работающих нитей выгоден тем, что чем больше нитей, тем сильнее они друг другу мешают. Гораздо эффективнее иметь 5-10 нитей-исполнителей и длинную очередь задач, чем создать 100 нитей для внезапно появившейся группы задач, которые будет конкурировать друг с другом за ресурсы: память, время процессора, доступ к базе и т.п.

Пример работы такого ThreadPoolExecutor:
<

Пример
ExecutorService service = Executors.newFixedThreadPool(2);

for(int i = 0; i < 10; i++)
{
 service.submit(new Runnable() {
    public void run()
    {
     // тут мы загружаем что-то тяжелое из интернета.
    }
 });
}

— Я что-то не вижу его…

— Объект ThreadPoolExecutor создается при вызове метода newFixedThreadPool.

Так вот, работает он очень просто. Как только ты добавляешь ему задачу с помощью метода submit, он:

А) Будит спящую нить для ее выполнения, если такая есть.

Б) Создает новую нить для выполнения задания, если ее нет.

В) Если достигнут максимум нитей, то просто кладет задачу в конец очереди.

Я специально написала в примере – тут мы загружаем что-то тяжелое из интернета. Если у нас есть 100 задач «скачать что-то большое из интернета», то нет смысла запускать много таких задач одновременно – мы упремся в ограничение ширины интернет-канала. В таком случает пары нитей должно быть достаточно. Именно это ты и видишь в примере выше:

ExecutorService service = Executors.newFixedThreadPool(2);

— Оказывается, работать с кучей задач не так уж и сложно.

— Да. Даже легче, чем ты можешь себе представить. Но об этом тебе расскажет Ким.