Теория и практика Java: Пулы потоков и очередь действий (исходники)Источник: IBM developerWorks Россия Брайан Гетц
Почему поток пулов?Работа многих серверных приложений, таких как Web-серверы, серверы базы данных, серверы файлов или почтовые серверы, связана с совершением большого количества коротких задач, поступающих от какого-либо удаленного источника. Запрос прибывает на сервер определенным образом, например, через сетевые протоколы (такие как HTTP, FTP или POP), через очередь JMS, или, возможно, путем опроса базы данных. Независимо от того, как запрос поступает, в серверных приложениях часто бывает, что обработка каждой индивидуальной задачи кратковременна, а количество запросов большое. Одной из упрощенных моделей для построения серверных приложений является создание нового потока каждый раз, когда запрос прибывает и обслуживание запроса в этом новом потоке. Этот подход в действительности хорош для разработки прототипа, но имеет значительные недостатки, что стало бы очевидным, если бы вам понадобилось развернуть серверное приложение, работающее таким образом. Один из недостатков подхода "поток-на-запрос" состоит в том, что системные издержки создания нового потока для каждого запроса значительны; a сервер, создавший новый поток для каждого запроса, будет тратить больше времени и потреблять больше системных ресурсов, создавая и разрушая потоки, чем он бы тратил, обрабатывая фактические пользовательские запросы. В дополнение к издержкам создания и разрушения потоков, активные потоки потребляют системные ресурсы. Создание слишком большого количества потоков в одной JVM (виртуальной Java-машине) может привести к нехватке системной памяти или пробуксовке из-за чрезмерного потребления памяти. Для предотвращения пробуксовки ресурсов, серверным приложениям нужны некоторые меры по ограничению количества запросов, обрабатываемых в заданное время. Поток пулов предлагает решение и проблемы издержек жизненного цикла потока, и проблемы пробуксовки ресурсов. При многократном использовании потоков для решения многочисленных задач, издержки создания потока распространяются на многие задачи. В качестве бонуса, поскольку поток уже существует, когда прибывает запрос, задержка, произошедшая из-за создания потока, устраняется. Таким образом, запрос может быть обработан немедленно, что делает приложение более быстрореагирующим. Более того, правильно настроив количество потоков в пуле потоков, вы можете предотвратить пробуксовку ресурсов, заставив любые запросы, если их количество выходит за определенные пределы, ждать до тех пор, пока поток не станет доступным, чтобы его обработать. Альтернативы пулов потоковПулы потока - это далеко не единственный способ использовать множественные потоки в серверном приложении. Как упоминалось ранее, иногда довольно разумно генерировать новый поток для каждой новой задачи. Однако, если частота создания задач высока, а их средняя продолжительность низка, порождение нового потока для каждой задачи приведет к проблемам с производительностью. Другая распространенная модель организации поточной обработки - наличие единого фонового потока и очереди задач для задач определенного типа. AWT (набор инструментальных средств для абстрактных окон) и Swing используют эту модель, в которой есть поток событий GUI (графического интерфейса пользователя), и вся работа, вызывающая изменения в пользовательском интерфейсе, должна выполняться в этом потоке. Однако, поскольку существует только один AWT-поток, нежелательно выполнять задачи в потоке AWT, завершение которого может занять значительное количество времени. В результате, приложения Swing часто требуют дополнительных потоков "исполнителя" для решения долгосрочных, связанных с пользовательским интерфейсом (UI) задач. Подходы "поток-на-задачу" и "единый фоновый поток" могут довольно хорошо функционировать в определенных ситуациях. Подход "поток-на-задачу" хорошо работаeт с небольшим количеством долгосрочных задач. Подход "единый фоновый поток" функционирует довольно хорошо, если не важна предсказуемость распределения (scheduling predictability), как в случае низкоприоритетных фоновых (low-priority background) задач. Однако большая часть серверных приложений ориентированы на обработку большого количества краткосрочных задач или подзадач, и нужно иметь механизм для эффективного осуществления этих задач с небольшими издержками, а также какую-либо меру управления ресурсами и предсказуемостью времени выполнения. Пулы потока дают следующие преимущества. Очереди действийС точки зрения того, как применяются пулы потоков, термин "пул потоков" несколько обманчив, "очевидное" применение пула потоков в большинстве случаев дает не тот результат, который мы хотели бы. Термин "пул потоков" связан с платформой Java и, возможно, является артефактом менее объектно-ориентированного подхода. Однако термин по-прежнему широко используется. Конечно, мы могли легко применять класс пула потоков, в котором класс клиентов ожидал бы доступного потока, передавал бы задачу этому потоку для исполнения и затем возвращал бы поток к пулу, когда все окончено; но этот подход имеет несколько потенциально нежелательных эффектов. Что, например, если пул пуст? Любая вызывающая сторона, которая предприняла попытку передать задачу потоку пулов, обнаружила бы, что пул пуст, и ее поток заблокировался бы, ожидая доступного потока пулов. Часто одной из причин, по которой нам бы хотелось использовать фоновые потоки, является необходимость предотвращения блокировки подающего (submitting) потока. Проталкивание блокировки к вызывающей стороне, что происходит в случае с "очевидным" применением пула потоков, может закончиться возникновением таких же проблем, какие мы пытались решить. То, что нам обычно нужно - это рабочая очередь в сочетании с фиксированной группой рабочих потоков, в которой используются
Возможно, вы заметили, что в реализации задач в Листинге 1 используется Пример рабочей очереди в Листинге 1 соответствует требованиям безопасного использования Возможный риск при использовании пулов потоковХотя пул потоков - мощный механизм для структурирования многопоточных приложений, он связан с определенным риском. Приложения, построенные при помощи пулов потоков, подвержены всем тем параллельным рискам, что и любое другое многопоточное приложение, как, например, ошибки синхронизации и взаимоблокировка, и также нескольким другим рискам, специфических также для пулов потоков, таких, как зависимая от пулов взаимоблокировка, пробуксовка ресурсов и рассеяние потока. ВзаимоблокировкаВ любом многопоточном приложении есть риск взаимоблокировки. Говорят, что набор процессов или потоков взаимоблокирован , когда каждый ожидает события, которое может быть вызвано другим процессом. Простейший случай взаимоблокировки - когда поток A полностью блокирует объект X и ожидает блокировки объекта Y, в то время как поток B полностью блокирует объект Y и ожидает блокировки объекта X. И если нет какого-либо способа вырваться из ожидания блокирования (что блокирующее устройство Java не поддерживает), взаимоблокированные потоки будут ожидать вечно. Поскольку взаимоблокировка - риск в любой многопоточной программе, пулы потоков предполагают другую возможность взаимоблокировки, где все потоки пулов осуществляют задачи, которые блокируются в ожидании результатов другой задачи в очереди, но эта задача не может запуститься, поскольку нет доступного незанятого потока. Это может случиться, когда пулы потоков используются для проведения имитационных экспериментов, включающих большое количество взаимодействующих объектов, имитационные объекты могут посылать запросы друг другу, которые потом выполняются как задачи из очереди, и запрашиваемый объект синхронно ожидает ответа. Пробуксовка ресурсовОдно из преимуществ пулов потоков состоит в том, что они обычно хорошо выполняют операции, имеющие отношение к альтернативным распределяющим механизмам, некоторые из которых мы уже обсудили. Но это верно только в том случае, если размер пула потоков настроен правильно. Потоки потребляют многочисленные ресурсы, включая память и другие системные ресурсы. Кроме памяти, требующейся для объекта Если пул потоков слишком велик, ресурсы, потребляемые этими потоками, могут в значительной степени повлиять на работу системы. Время будет напрасно потрачено на переключение между потоками, и если у вас потоков больше, чем необходимо, это может вызвать проблемы ресурсного голодания, так кака потоки пулов потребляют ресурсы, которые могли бы быть более эффективно использованы другими задачами. В дополнение к ресурсам, использующимися самими потоками, работа, выполняемая по обслуживанию запросов, может также требовать дополнительных ресурсов, таких как соединения JDBC, сокеты, или файлы. Эти ресурсы также ограничены, и слишком много параллельных запросов могут вызвать сбои, такие как невозможность определении места JDBC-соединения. Параллельные ошибкиПулы потоков и другие механизмы ведения очередей опираются на методы Утечка потокаСущественный риск в самых разных пулах потоков заключается в утечке потока, которая случается, когда поток удаляется из пула для выполнения задачи, но не возвращается в пул, когда задача выполнена. Во-первых, это происходит, когда задача выдает Задачи, которые постоянно блокируются, например, те, что потенциально ждут ресурсов, которые могут и не стать доступными, или ждут ввода со стороны пользователя, который, возможно, ушел домой, могут также вызвать эффект, эквивалентный утечке потока. Если поток постоянно занимается такой задачей, он фактически удален из пула. Таким задачам следует либо выделять собственный поток, либо ограничить время ожидания. Перегрузка запросамиБывает, что сервер просто переполнен запросами. В этом случае мы, возможно, не захотим, чтобы каждый входящий запрос помещался в нашу рабочую очередь, потому, что задачи, ожидающие выполнения, могут потреблять слишком много системных ресурсов и вызвать ресурсное голодание. Вам решать, что делать в таком случае; в некоторых ситуациях вы, возможно, можете просто выбросить запрос, надеясь, что протоколы более высокого уровня повторят запрос позже, или, возможно, вы захотите отказаться от запроса, сообщив, что сервер временно занят. Руководство по эффективному использованию пулов потоковПулы потоков могут быть чрезвычайно эффективным способом структурирования серверных приложений, при условии, что вы следуете некоторым простым правилам:
Настройка размера пулаНастраивая размер пула потоков, важно избежать двух ошибок: слишком мало потоков или слишком много потоков. К счастью, для большинства приложений спектр между слишком большим и слишком малым количеством потоков довольно широк. Если вы помните, есть два основных преимущества в организации поточной обработки сообщений в приложениях: возможность продолжения процесса во время ожидания медленных операций, таких, как I/O (ввод - вывод), и использование возможностей нескольких процессоров. В приложениях с ограничением по скорости вычислений, функционирующих на N-процессорной машине, добавление дополнительных потоков может улучшить пропускную способность, по мере того как количество потоков подходит к N, но добавление дополнительных потоков свыше N не оправдано. Действительно, слишком много потоков разрушают качество функционирования из-за дополнительных издержек переключения процессов Оптимальный размер пула потоков зависит от количества доступных процессоров и природы задач в рабочей очереди. На N-процессорной системе для рабочей очереди, которая будет выполнять исключительно задачи с ограничением по скорости вычислений, вы достигните максимального использования CPU с пулом потоков, в котором содержится N или N+1 поток. Для задач, которые могут ждать осуществления I/O (ввода - вывода) -- например, задачи, считывающей HTTP-запрос из сокета - вам может понадобиться увеличение размера пула свыше количества доступных процессоров, потому, что не все потоки будут работать все время. Используя профилирование, вы можете оценить отношение времени ожидания (WT) ко времени обработки (ST) для типичного запроса. Если назвать это соотношение WT/ST, для N-процессорной системе вам понадобится примерно N*(1+WT/ST) потоков для полной загруженности процессоров. Использование процессора - не единственный фактор, важный при настройке размера пула потоков. По мере возрастания пула потоков, можно столкнуться с ограничениями планировщика, доступной памяти, или других системных ресурсов, таких, как количество сокетов, дескрипторы открытого файла, или каналы связи базы данных Нет необходимости писать своеДаг Ли создал отличную открытую библиотеку утилит параллельности, Библиотека ЗаключениеПул потока - полезный инструмент для организации серверов приложений. Он довольно простой по сути, но есть некоторые моменты, с которыми следует быть осторожными во время применения и использования, такие как взаимоблокировка, пробуксовка ресурсов, и сложности, связанные с |