Я всегда читал, что создание распараллеливает, является дорогим.
Я также знаю, что Вы не можете повторно выполнить поток.
Я вижу в документе Executors
класс:
Создает пул потоков, который создает новые потоки по мере необходимости, но снова использует ранее созданные потоки, когда они доступны.
Следите за словом 'повторное использование'.
Как пулы потоков 'снова используют' потоки?
Кажется, я понял, что вас смущает, поэтому вот мой более пространный ответ: терминология немного вводит в заблуждение (очевидно, иначе вы бы не задавали этот вопрос, специально делая акцент на "повторном использовании"):
Как пулы потоков "повторно используют" потоки?
Происходит то, что один поток может использоваться для обработки нескольких задач (обычно передаваемых как Runnable
, но это зависит от вашего "исполнителя": стандартные исполнители принимают Runnable
, но вы можете написать свой собственный "исполнитель". / thread-pool, принимающий что-то более сложное, чем Runnable
[как, скажем, CancellableRunnable
]).
Теперь в стандартной реализации ExecutorService
, если поток каким-то образом завершается в процессе использования, он автоматически заменяется новым потоком, но это не то "повторное использование", о котором они говорят. В этом случае нет никакого "повторного использования".
Итак, верно, что вы не можете вызвать start()
на Java Thread дважды но вы можете передать исполнителю столько Runnable
, сколько хотите, и метод Runnable
каждого run()
будет вызван один раз.
Вы можете передать 30 Runnable
5 Java Thread
и каждый рабочий поток может вызывать, например, run()
6 раз (практически нет гарантии, что вы будете выполнять именно 6 Runnable
на Thread
, но это уже детали).
В этом примере start()
был бы вызван 6 раз. Каждый из этих 6 start()
вызовет ровно один раз метод run()
каждого Thread
:
Из Thread.start()
Javadoc:
* Причина начала выполнения этого потока; виртуальная машина Java * вызывает метод
run
этого потока.
НО затем внутри метода run()
каждого потока Runnable
будет декеирован и будет вызван метод run()
каждого Runnable
. Таким образом, каждый поток может обрабатывать несколько Runnable
. Это то, что называют "повторным использованием потоков".
Один из способов сделать свой собственный пул потоков - использовать блокирующую очередь, в которую вы записываете runnables, и каждый из ваших потоков, закончив обработку run()
метода Runnable
, выписывает следующий Runnable
(или блок) и запускает его run()
метод, затем промывает и повторяет.
Я полагаю, что часть путаницы (а она есть) происходит из-за того, что Thread
принимает Runnable
и при вызове start()
вызывается метод run()
Runnable, а пулы потоков по умолчанию also принимают Runnable
.
Пул потоков состоит из ряда фиксированных рабочих потоков, которые могут принимать задачи из внутренней очереди задач. Таким образом, если одна задача завершается, поток не завершает , а ожидает следующей задачи. Если вы прерываете поток, он автоматически заменяется.
Метод run
потоков в пуле потоков не состоит только из выполнения одной задачи. Метод run
потока в пуле потоков содержит цикл. Он извлекает задачу из очереди, выполняет задачу (которая возвращает обратно в цикл по завершении), а затем получает следующую задачу. Метод run
не завершается, пока поток больше не будет нужен.
Отредактировано для добавления:
Вот метод run
внутреннего класса Worker
в ThreadPoolExecutor .
696: /**
697: * Main run loop
698: */
699: public void run() {
700: try {
701: Runnable task = firstTask;
702: firstTask = null;
703: while (task != null || (task = getTask()) != null) {
704: runTask(task);
705: task = null; // unnecessary but can help GC
706: }
707: } finally {
708: workerDone(this);
709: }
710: }