Технический фон пула потоков
В объектно-ориентированном программировании создание и уничтожение объектов занимает много времени, потому что для создания объекта требуется ресурсы памяти или другие больше ресурсов. Это еще больше, в Java, где виртуальная машина будет пытаться отслеживать каждый объект, чтобы его можно было собрать мусор после уничтожения объекта.
Следовательно, одним из способов повышения эффективности сервисных программ является минимизация количества раз создания и уничтожения объектов, особенно некоторые очень ресурсные объекты, создание и разрушение объектов. Как использовать существующие объекты для обслуживания - это ключевой вопрос, который необходимо решить. На самом деле, это причина, по которой производятся некоторые технологии «объединения ресурсов».
Например, многие общие компоненты, обычно наблюдаемые в Android, обычно неразделимы от концепции «пула», таких как различные библиотеки загрузки изображений и библиотеки сетевых запросов. Даже если Meaasge в механизме обмена сообщениями Android использует meaasge.obtain (), это объект в используемом пуле Meaasge, поэтому эта концепция очень важна. Технология объединения потоков, представленная в этой статье, также соответствует этой идее.
Преимущества пула потоков:
1. Повторно используйте потоки в пуле потоков, чтобы уменьшить накладные расходы на производительность, вызванную созданием и разрушением объекта;
2. Он может эффективно контролировать максимальное количество потоков потоков, улучшить использование системных ресурсов, избежать чрезмерной конкуренции за ресурсами и избегать блокировки;
3. Возможность выполнять простое управление несколькими потоками, что делает использование потоков простым и эффективным.
Исполнитель Pool Framework Pool
Пул потоков в Java реализуется через структуру исполнителя. Структура исполнителя включает в себя классы: исполнители, исполнители, исполнители, TreadPoolexecutor, Callible и Future, использование FutureTask и т. Д.
Исполнитель: Есть только один метод для всех интерфейсов пула потоков.
Исполнитель публичного интерфейса {void execute (runnable command); }Исполнители: добавление поведения исполнителя является наиболее прямым интерфейсом класса реализации исполнителя.
Исполнители: предоставляет серию заводских методов для создания пула потоков, а возвращаемые пулы потоков все реализуют интерфейс Executorservice.
ThreadPoolexeCutor: конкретный класс реализации пулов потоков. Различные пулы потоков, как правило, реализованы на основе этого класса. Метод строительства следующим образом:
public ThreadPoolexeCutor (int corePoolsize, int maximumpoolsize, Long KeepaliveTime, TimeUnit Unit, Blockquequeue <Runnable> workqueue) {this (corepoolsize, maximumpoolsize, keepalivetime, Unit, rabqueue, executors.defaultepactory (), Defulthandler);CorePoolsize: количество резьбов в пуле потоков и количество потоков, работающих в пуле потоков, никогда не будет превышать CorePoolsize и может выжить по умолчанию. Вы можете установить AllingCoreThreadTimeOut на True, количество основных потоков составляет 0, а KeepALivetime контролирует время ожидания всех потоков.
maximumpoolsize: максимальное количество потоков, разрешенных пулом резьбов;
KeepAliveTime: относится к времени ожидания, когда заканчивается холостое время;
Единица: это перечисление, представляющее единицу KeepaliveTime;
Workqueue: представляет собой блокировку <runnable queue, в которой хранится задача.
Blockingqueue: Blockqueue - это инструмент, который в основном используется для управления синхронизацией потока под java.util.concurrent. Если Blockqueue пуста, операция по получению чего -либо из блокировки будет заблокирована и не будет пробуждена, пока Blockequeue не войдет в вещь. Точно так же, если BlockingQueue заполнена, любая операция, которая пытается хранить вещи в нем, будет заблокирована и не будет пробуждена, чтобы продолжить работу, пока не появится место в блокировке. Блокирующие очереди часто используются в сценариях производителей и потребителей. Производители - это потоки, которые добавляют элементы в очереди, а потребители - это потоки, которые берут элементы из очередей. Блокирующая очередь - это контейнер, где производитель хранит элементы, а потребитель получает только элементы из контейнера. Конкретные классы реализации включают в себя LinkedBlockqueue, ArrayBlockingqueed и т. Д. Как правило, внутренняя блокировка и пробуждение достигаются посредством блокировки и состояния (обучение и использование блокировки и условий дисплея).
Рабочий процесс пула потоков выглядит следующим образом:
Когда пул потоков был впервые создан, внутри не было нити. Очередь задачи передается как параметр. Однако, даже если в очереди есть задачи, пул потоков не выполнит их немедленно.
При добавлении задачи, вызовом метода execute (), пул потоков вынесет следующее суждение:
Если количество запущенных потоков меньше, чем corepoolsize, создайте поток для немедленного выполнения этой задачи;
Если количество запущенных потоков больше или равное CorePoolsize, то поместите эту задачу в очередь;
Если в настоящее время очередь полна, а количество запущенных потоков меньше, чем Maximumpoolsize, то вам все равно нужно создать неточный поток для немедленного запуска задачи;
Если очередь заполнена, а количество запущенных потоков больше или равносильно maximumpoolsize, пул потоков бросит исключение RejectexecutionException.
Когда поток выполняет задачу, для выполнения очереди требуется следующая задача.
Когда нить не имеет ничего общего, и через определенный период времени (HeapAliveTime) пул потоков будет судить, что, если количество работающих в настоящее время будет больше, чем CorePoolsize, то поток будет остановлен. Таким образом, после того, как все задачи пула потоков будут выполнены, он в конечном итоге уменьшится до размера CorePoolsize.
Создание и использование пулов потоков
Пул потоков генерации использует статический метод исполнителей класса инструментов. Ниже приведены несколько общих пулов резьбов.
SingleThreadExeCutor: отдельный фоновый поток (очередь буфера не ограничена)
Public Static ExecutorService NewsingleThreadExeCutor () {return New FinalizedElegatedExeCutorService (New ThreadPoolexeCutor (1, 1, 0L, TimeUnit.Milliseconds, New LinkedBlockequeue <Runnable> ())); }Создайте один бассейн потоков. Этот пул потоков имеет только один работающий поток, который эквивалентен одним потоке, выполняющим все задачи в сериале. Если этот уникальный поток заканчивается из -за исключения, то будет новый поток, чтобы заменить его. Этот пул потоков гарантирует, что порядок выполнения всех задач выполняется в порядке подачи задачи.
FixedThreadpool: только пул резьбов из резьбов с фиксированным размером (очередь буфера неограничена).
Public Static Executorsers NewFixedThreadPool (int nthreads) {
вернуть новый ThreadPoolexeCutor (nthreads, nThreads,
0l, TimeUnit.milliseconds,
новый LinkedBlockingqueue <Runnable> ());
}
Создайте пул потоков фиксированного размера. Каждый раз, когда задача отправляется, поток создается до тех пор, пока поток не достигнет максимального размера пула резьбов. Размер пула потоков остается таким же, когда он достигнет своего максимального значения. Если поток заканчивается из -за исключения выполнения, пул потоков добавит новый поток.
CachedThreadpool: безграничный бассейн резьбов, может выполнить автоматическую переработку потоков.
public static executorservice newcachedthreadpool () {return new ThreadPoolexeCutor (0, integer.max_value, 60L, TimeUnit.seconds, New SynchronousQueue <lunnable> ()); }Если размер пула потоков превышает поток, необходимый для обработки задачи, некоторые из потоков холостого хода (без выполнения задачи за 60 секунд) будут переработаны. Когда количество задач увеличивается, этот пул потоков может разумно добавлять новые потоки для обработки задачи. Этот пул потоков не ограничивает размер пула потоков, который полностью зависит от максимального размера потока, который может создать операционная система (или JVM). Synchronousqueue - это блокирующая очередь с буфером 1.
PREDULEDTHREADPOOL: пул резьбов ядра с пулом потоков с фиксированным ядром, неограниченный размер. Этот пул потоков поддерживает необходимость периодически и периодически выполнять задачи.
Public Static ExecutorService NewschedDThreadPool (int corePoolsize) {return new cheduledThreadpool (corepoolsize, integer.max_value, default_keepalive_millis, Milliseconds, new DelesedWorkeaue ()); }Создайте пул потоков, который периодически выполняет задачи. Если простаиваться, неточный пул потоков будет переработан в время default_keepalivemillis.
Есть два наиболее часто используемых метода подачи задач в пулах потоков:
выполнять:
Executorservice.execute (выполняемый запуск);
Представлять на рассмотрение:
FutureTask task = rececusterService.submit (запускаемой запуска);
FutureTask <t> task = executorservice.submit (Runnable Runnable, t Results);
FutureTask <t> task = rececusterservice.submit (callible <t> callable);
То же самое относится и к реализации отправки (Callible Callible) и той же применимости для отправки (Runnable Runnable).
public <t> future <t> отправить (callable <t> task) {if (task == null) бросить новый NullPointerException (); FutureTask <t> ftask = newtaskfor (задача); выполнить (ftask); вернуть ftask;}Можно видеть, что отправка - это задача, которая возвращает результат, и вернет объект FutureTask, так что результат можно получить с помощью метода get (). Окончательный вызов для отправки также выполняется (Runnable Runable). Отправьте только инкапсулирование вызываемого объекта или запустить в объект FutureTask. Поскольку FutureTask является запущенным, его можно выполнить в выполнении. Для того, как вызываемые объекты и выполняются, инкапсулируются в объекты FutureTask, см. Использование Callable, Future, FutureTask.
Принцип реализации пула потоков
Если вы говорите только об использовании пулов потоков, то этот блог не имеет большой ценности. В лучшем случае это просто процесс ознакомления с API, связанным с исполнителем. В процессе реализации пула потоков не используется синхронизированное ключевое слово, но использует летучие, блокировки и синхронные (блокирующие) очереди, классы, связанные с атомной, FutureTask и т. Д., Поскольку у последнего есть лучшая производительность. Процесс понимания может хорошо изучить идею параллельного управления в исходном коде.
Преимущества объединения потоков, упомянутые в начале, обобщены следующим образом:
Повторное использование потока
Контролировать максимальное количество параллелей
Управлять темами
1. Процесс мультиплексирования потока
Чтобы понять принцип мультиплексирования потока, вы должны сначала понять жизненный цикл потока.
В течение жизненного цикла потока он должен пройти через пять штатов: новые, бегущие, бегущие, заблокированные и мертвые.
Поток создает новую ветку через новый. Этот процесс состоит в том, чтобы инициализация некоторой информации потока, такой как имя потока, идентификатор, группа, к которой принадлежит поток и т. Д., Который можно считать просто обычным объектом. После вызова Thread's start () виртуальная машина Java создает для него стек вызовов и программный счетчик метода, и в то же время приводит к тому, что при вызове метода начала будет возникнуть исключение.
Поток в этом состоянии не начинает работать, но только означает, что поток может работать. Что касается того, когда поток начинает работать, это зависит от планировщика в JVM. Когда поток получает процессор, будет вызван метод run (). Не звоните по методу Thread's run () самостоятельно. Затем переключитесь между готовым блокировкой в соответствии с планированием процессора, пока метод run () не закончится, или другие методы не остановит поток и не войдут в мертвое состояние.
Следовательно, принцип реализации повторного использования потока должен состоять в том, чтобы сохранить поток в живых (готовые, бег или блокировка). Затем давайте посмотрим, как ThreadPoolexecutor реализует повторное использование потока.
Главный класс работников в повторном использовании потока ThreadPoolexeCutor. Взгляните на упрощенный код класса работников, так что его легко понять:
Приватный финальный класс Работник реализует runnable {final Thread Thread; Runnable FirstTask; Worker (Runnable FirstTask) {this.firstSk = FirstTask; this.Thread = getThreadFactory (). NewThread (This);} public void Run () {RunWorker (This);} Final void RunWorker (worker w) {Runnable Task = askwork = usdable = задание = w.firsttask; w.firsttask = null; while (task!Рабочий - это запуска и имеет нить одновременно. Этот поток - это поток, который будет открыт. При создании нового рабочего объекта новый объект потока создается одновременно, и сам работник передается в Tthread в качестве параметра. Таким образом, когда называется метод потока start (), метод работника run () фактически работает. Затем в Runworker () есть цикл Whink, который продолжает получать запускаемый объект из getTask () и выполняет его последовательно. Как GetTask () получает заполненный объект?
Все еще упрощенный код:
Private Runnable getTask () {if (некоторые особые случаи) {return null; } Runnable r = wormqueue.take (); return r;}Эта рабочая очередь - очередь блокировки, которая хранит задачи при инициализации ThreadPoolexeCutor. В очереди хранится выполненные задачи, которые будут выполнены. Поскольку Blockqueueue - это блокирующая очередь, BlockingQueue.take () пуст и входит в состояние ожидания, пока не будет добавлен новый объект в BlockingQueue, чтобы разбудить заблокированную нить. Следовательно, в целом метод потока run () не закончится, но будет продолжать выполнять выполняемые задачи из Workqueue, что достигает принципа повторного использования потока.
2. контролировать максимальное количество параллелей
Итак, когда запустите в рабочую силу? Когда создается работник? Когда поток в работнике называется start (), чтобы открыть новый поток, чтобы выполнить метод работника run ()? Приведенный выше анализ показывает, что Runworker () в работнике выполняет задачи один за другим, по секунду, так как же сама проявляется в параллелистике?
Легко подумать, что вы выполните некоторые из вышеперечисленных задач, когда вы выполняете (запустить). Посмотрим, как это делается в выполнении.
выполнять:
Упрощенный код
public void execute (runnable command) {if (command == null) бросить новое nullpointerException (); int c = ctl.get (); // Текущее количество потоков <corepoolsizeif (workercountof (c) <corepoolsize) {// напрямую запустить новый поток. if (addworker (command, true)) return; c = ctl.get ();} // количество активных потоков> = corepoolsize // runstate работает && queue не полная if (iSrunning (c) && hairpqueue.offer (command)) {int conceck = ctl.get (); // проверяет, является ли он und-range in range rain-queme, если он снова выполняет. (! Isrunning (Receck) && remove (command)) DRIECT (CONDAM); // Использование стратегии, указанной пулом потоков, чтобы отвергнуть задачу // два случая: // 1. Непонятие состояния отвергает новые задачи // 2. В очереди полна и не удалось запустить новую нить (WorkCount> MaximumpoolSize)} ense (!AddWorker:
Упрощенный код
Private Boolean AddWorker (Runnable FirstTask, Boolean Core) {int wc = workercountof (c); if (wc> = (core? corepoolsize: maximumpoolsize)) {return false;} w = new Worker (FirstTask); Final Thread T = W.Thread; T.Start ();};Согласно коде, давайте рассмотрим вышеупомянутую ситуацию добавления задач во время работы пула потоков:
* Если количество запущенных потоков меньше, чем CorePoolSize, создайте поток для немедленного выполнения этой задачи;
* Если количество запущенных потоков больше или равное CorePoolSize, то поместите эту задачу в очередь;
* Если в это время очередь полна, а количество запущенных потоков меньше, чем максимально, то вам все равно нужно создать неточный поток, чтобы немедленно запустить задачу;
* Если очередь заполнена, а количество запущенных потоков больше или равносильно максимумам, пул потоков бросит исключение RejectexecutionException.
Вот почему Asynctask's Asynctask выполняется параллельно и превышает максимальное количество задач и бросков DepjectExecutionException. Для получения подробной информации, пожалуйста, обратитесь к последней версии интерпретации исходного кода Asynctask и темной стороны Asynctask
Если новый поток успешно создан через AddWorker, start () и используйте FirstTask в качестве первой задачи, выполненной в run () в этом работнике.
Хотя задача каждого работника - последовательная обработка, если созданы несколько работников, поскольку они разделяют рабочую силу, они будут обрабатываться параллельно.
Следовательно, максимальный номер параллелизма контролируется в соответствии с CorePoolsize и Maximumpoolsize. Общий процесс может быть представлен на рисунке ниже.
Приведенное выше объяснение и картинки могут быть хорошо поняты.
Если вы занимаетесь разработкой Android и знакомы с принципами обработчика, вы можете подумать, что эта картина довольно знакома. Некоторые из процессов очень похожи на те, которые используются Handler, Looper и Meaasge. Handler.send (сообщение) эквивалентно выполнению (Runnuble). Очередь Meaasge, поддерживаемая в Looper, эквивалентна BlockingQueue. Тем не менее, вам необходимо поддерживать эту очередь путем синхронизации. Функция Loop () в петлях Looper, чтобы вывести MeaAsge из очереди MeaAsge, а Runwork () у работника непрерывно выполняется от BlockingQueue.
3. Управление потоками
Через пул потоков мы можем управлять повторным использованием потока, контролировать номер параллелистика и уничтожить процессы. Повторное использование и контроль над использованием и контролем была обсуждена выше, и процесс управления потоком был вкраплен в нем, что легко понять.
В ThreadPoolexeCutor существует переменная CTL AtomicInteger. Два содержимого сохраняется через эту переменную:
Количество всех потоков. Каждая потока находится в состоянии, где хранятся нижние 29 бит резьбов, а более высокие 3 бита Runstate хранятся. Различные значения получают с помощью операций битов.
Частный окончательный AtomicInteger ctl = new Atomicinteger (ctlof (running, 0)); // Получить государственное государство поток. Емкость;} // термин независимо от того, работает ли поток частным статическим логическим Isrunning (int c) {return c <opplowd;}Здесь процесс выключения пула потоков в основном анализируется с помощью Shutdown и ShutdownNow (). Прежде всего, пул потоков имеет пять состояний для управления добавлением задач и выполнением задач. Введены следующие три основных типа:
Запуск статуса: Пул потоков работает нормально и может принимать новые задачи и задачи процесса в очереди;
Статус выключения: никаких новых задач не принимаются, но задачи в очереди будут выполнены;
Статус остановки: больше никаких задач больше не принимаются, а остановка задачи не обрабатывается в очереди. Этот метод установит runstate для выключения и завершит все потоки холостого хода, в то время как потоки, которые все еще работают, не затронуты, поэтому на человека в очереди будет выполнено.
Метод ShutdownNow устанавливает RunState для остановки. Разница между методом выключения, этот метод завершит все потоки, поэтому задачи в очереди не будут выполняться.
Резюме: Благодаря анализу исходного кода ThreadPoolexeCutor мы имеем общее понимание процесса создания пулов потоков, добавления задач и выполнения пулов потоков. Если вы знакомы с этими процессами, будет проще использовать пулы потоков.
Некоторые из контроля параллелизма, полученного из него, и использование обработки задач моделей-производителей-потребителей будет очень полезно для понимания или решения других связанных проблем в будущем. Например, механизм обработчика в Android и очередь Messager в Looper также в порядке, чтобы использовать Blookqueue для его обработки. Это усиление чтения исходного кода.
Выше приведено информация, разбирающая пул ниток Java. Мы будем продолжать добавлять соответствующую информацию в будущем. Спасибо за поддержку этого сайта!