Ожидание выполнения потоков java

Чем процесс отличается от потока?

Потоки выполнения процесса можно уподобить нескольким вместе работающим поварам. Все они готовят одно блюдо, читают одну и ту же кулинарную книгу с одним и тем же рецептом и следуют его указаниям, причём необязательно все они читают на одной и той же странице.

Поток Процесс
Поток имеет стэк – свою память для исполнения. Процесс – это совокупность кода и данных, финкционирующих в виртуальном (адресном) пространстве.
Потоки выполнения существуют как составные элементы процессов. Процессы, как правило, независимы.
Несколько потоков выполнения внутри процесса совместно используют информацию о состоянии, а также память и другие вычислительные ресурсы. Несут значительно больше информации о состоянии.
Потоки выполнения совместно используют их адресное пространство. Операционная система (ОС) для каждого процесса создает своё, так называемое «виртуальное адресное пространство» в памяти, к которому процесс имеет прямой доступ.
Взаимодействуют только через предоставляемые системой механизмы связей между процессами (файлы, каналы связи..)
Переключение контекста между потоками выполнения в одном процессе, как правило, быстрее, чем переключение контекста между процессами.
Потоки расходуют существенно меньше ресурсов, чем процессы, в процессе выполнения работы выгоднее создавать дополнительные потоки и избегать создания новых процессов.

Когда запускается любое приложение, то начинает выполняться поток, называемый главным потоком (main). От него порождаются дочерние потоки.

Главный поток, как правило, является последним потоком, завершающим выполнение программы.

Несмотря на то, что главный поток создаётся автоматически, им можно управлять через объект класса Thread. Для этого нужно вызвать метод currentThread(), после чего можно управлять потоком.

Класс Thread содержит несколько методов для управления потоками:

В нем пишется выполняемый код

Запускает переопределенный метод run()

* Если просто запустить run() не будет параллельности выполнения — просто выполниться метод run().

Источник

ExecutorService — Ожидание завершения потоков

ФреймворкExecutorService упрощает обработку задач в нескольких потоках. Мы собираемся проиллюстрировать некоторые сценарии, в которых мы ждем, пока потоки завершат свое выполнение.

Кроме того, мы покажем, как корректно завершить работуExecutorService и дождаться, пока уже запущенные потоки завершат свое выполнение.

2. После выключенияExecutor’s

При использованииExecutor, мы можем выключить его, вызвав методыshutdown() илиshutdownNow(). Although, it won’t wait until all threads stop executing.

Ожидание завершения выполнения существующих потоков может быть достигнуто с помощью методаawaitTermination().

Это блокирует поток до тех пор, пока все задачи не завершат свое выполнение или не истечет указанное время ожидания:

public void awaitTerminationAfterShutdown(ExecutorService threadPool) < threadPool.shutdown(); try < if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) < threadPool.shutdownNow(); >> catch (InterruptedException ex) < threadPool.shutdownNow(); Thread.currentThread().interrupt(); >>

3. ИспользуяCountDownLatch

Затем давайте рассмотрим другой подход к решению этой проблемы — использованиеCountDownLatch для сигнализации о завершении задачи.

Мы можем инициализировать его значением, которое представляет, сколько раз он может быть уменьшен до того, как будут уведомлены все потоки, вызвавшие методawait().

Например, если нам нужно, чтобы текущий поток ожидал, пока другой потокN завершит свое выполнение, мы можем инициализировать защелку, используяN:

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10); CountDownLatch latch = new CountDownLatch(2); for (int i = 0; i < 2; i++) < WORKER_THREAD_POOL.submit(() -> < try < // . latch.countDown(); >catch (InterruptedException e) < Thread.currentThread().interrupt(); >>); > // wait for the latch to be decremented by the two remaining threads latch.await();

4. ИспользуяinvokeAll()

Первый подход, который мы можем использовать для запуска потоков, — это методinvokeAll(). The method returns a list of Future objects after all tasks finish or the timeout expires.

Также мы должны отметить, что порядок возвращаемых объектовFuture такой же, как и список предоставленных объектовCallable:

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10); List> callables = Arrays.asList( new DelayedCallable("fast thread", 100), new DelayedCallable("slow thread", 3000)); long startProcessingTime = System.currentTimeMillis(); List> futures = WORKER_THREAD_POOL.invokeAll(callables); awaitTerminationAfterShutdown(WORKER_THREAD_POOL); long totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue(totalProcessingTime >= 3000); String firstThreadResponse = futures.get(0).get(); assertTrue("fast thread".equals(firstThreadResponse)); String secondThreadResponse = futures.get(1).get(); assertTrue("slow thread".equals(secondThreadResponse));

5. ИспользуяExecutorCompletionService

Другой подход к запуску нескольких потоков — использованиеExecutorCompletionService.. Он использует предоставленныйExecutorService для выполнения задач.

Одно отличие отinvokeAll() — это порядок, в котором возвращаютсяFutures,, представляющие выполненные задачи. ExecutorCompletionService uses a queue to store the results in the order they are finished, аinvokeAll() возвращает список, имеющий тот же последовательный порядок, что и созданный итератором для данного списка задач:

CompletionService service = new ExecutorCompletionService<>(WORKER_THREAD_POOL); List callables = Arrays.asList( new DelayedCallable("fast thread", 100), new DelayedCallable("slow thread", 3000)); for (Callable callable : callables)

Доступ к результатам можно получить с помощью методаtake():

long startProcessingTime = System.currentTimeMillis(); Future future = service.take(); String firstThreadResponse = future.get(); long totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue("First response should be from the fast thread", "fast thread".equals(firstThreadResponse)); assertTrue(totalProcessingTime >= 100 && totalProcessingTime < 1000); LOG.debug("Thread finished after: " + totalProcessingTime + " milliseconds"); future = service.take(); String secondThreadResponse = future.get(); totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue( "Last response should be from the slow thread", "slow thread".equals(secondThreadResponse)); assertTrue( totalProcessingTime >= 3000 && totalProcessingTime < 4000); LOG.debug("Thread finished after: " + totalProcessingTime + " milliseconds"); awaitTerminationAfterShutdown(WORKER_THREAD_POOL);

6. Заключение

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

CountDownLatch полезен, когда нам нужен механизм для уведомления одного или нескольких потоков о завершении набора операций, выполняемых другими потоками.

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

Исходный код статьи доступенover on GitHub.

Источник

Читайте также:  Passing type in java
Оцените статью