Elettracompany.com

Компьютерный справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Java util concurrent

Многопоточное программирование в Java 8. Часть первая. Параллельное выполнение кода с помощью потоков

    Переводы, 8 июля 2015 в 16:58

Добро пожаловать в первую часть руководства по параллельному программированию в Java 8. В этой части мы на простых примерах рассмотрим, как выполнять код параллельно с помощью потоков, задач и сервисов исполнителей.

Впервые Concurrency API был представлен вместе с выходом Java 5 и с тех пор постоянно развивался с каждой новой версией Java. Большую часть примеров можно реализовать на более старых версиях, однако в этой статье я собираюсь использовать лямбда-выражения. Если вы все еще не знакомы с нововведениями Java 8, рекомендую посмотреть мое руководство.

Потоки и задачи

Все современные операционные системы поддерживают параллельное выполнение кода с помощью процессов и потоков. Процесс — это экземпляр программы, который запускается независимо от остальных. Например, когда вы запускаете программу на Java, ОС создает новый процесс, который работает параллельно другим. Внутри процессов мы можем использовать потоки, тем самым выжав из процессора максимум возможностей.

Потоки (threads) в Java поддерживаются начиная с JDK 1.0. Прежде чем запустить поток, ему надо предоставить участок кода, который обычно называется «задачей» (task). Это делается через реализацию интерфейса Runnable , у которого есть только один метод без аргументов, возвращающий void — run() . Вот пример того, как это работает:

Поскольку интерфейс Runnable функциональный, мы можем использовать лямбда-выражения, которые появились в Java 8. В примере мы создаем задачу, которая выводит имя текущего потока на консоль, и запускаем ее сначала в главном потоке, а затем — в отдельном.

Результат выполнения этого кода может выглядеть так:

Из-за параллельного выполнения мы не можем сказать, будет наш поток запущен до или после вывода «Done!» на экран. Эта особенность делает параллельное программирование сложной задачей в больших приложениях.

Sportmaster Lab, Москва

Потоки могут быть приостановлены на некоторое время. Это весьма полезно, если мы хотим сэмулировать долго выполняющуюся задачу. Например, так:

Когда вы запустите этот код, вы увидите секундную задержку между выводом первой и второй строки на экран. TimeUnit — полезный класс для работы с единицами времени, но то же самое можно сделать с помощью Thread.sleep(1000) .

Работать с потоками напрямую неудобно и чревато ошибками. Поэтому в 2004 году в Java 5 добавили Concurrency API. Он находится в пакете java.util.concurrent и содержит большое количество полезных классов и методов для многопоточного программирования. С тех пор Concurrency API непрерывно развивался и развивается.

Давайте теперь подробнее рассмотрим одну из самых важных частей Concurrency API — сервис исполнителей (executor services).

Исполнители

Concurrency API вводит понятие сервиса-исполнителя (ExecutorService) — высокоуровневую замену работе с потоками напрямую. Исполнители выполняют задачи асинхронно и обычно используют пул потоков, так что нам не надо создавать их вручную. Все потоки из пула будут использованы повторно после выполнения задачи, а значит, мы можем создать в приложении столько задач, сколько хотим, используя один исполнитель.

Вот как будет выглядеть наш первый пример с использованием исполнителя:

Класс Executors предоставляет удобные методы-фабрики для создания различных сервисов исполнителей. В данном случае мы использовали исполнитель с одним потоком.

Результат выглядит так же, как в прошлый раз. Но у этого кода есть важное отличие — он никогда не остановится. Работу исполнителей надо завершать явно. Для этого в интерфейсе ExecutorService есть два метода: shutdown() , который ждет завершения запущенных задач, и shutdownNow() , который останавливает исполнитель немедленно.

Вот как я предпочитаю останавливать исполнителей:

Исполнитель пытается завершить работу, ожидая завершения запущенных задач в течение определенного времени (5 секунд). По истечении этого времени он останавливается, прерывая все незавершенные задачи.

Callable и Future

Кроме Runnable , исполнители могут принимать другой вид задач, который называется Callable . Callable — это также функциональный интерфейс, но, в отличие от Runnable , он может возвращать значение.

Давайте напишем задачу, которая возвращает целое число после секундной паузы:

Callable-задачи также могут быть переданы исполнителям. Но как тогда получить результат, который они возвращают? Поскольку метод submit() не ждет завершения задачи, исполнитель не может вернуть результат задачи напрямую. Вместо этого исполнитель возвращает специальный объект Future, у которого мы сможем запросить результат задачи.

После отправки задачи исполнителю мы сначала проверяем, завершено ли ее выполнение, с помощью метода isDone() . Поскольку задача имеет задержку в одну секунду, прежде чем вернуть число, я более чем уверен, что она еще не завершена.

Вызов метода get() блокирует поток и ждет завершения задачи, а затем возвращает результат ее выполнения. Теперь future.isDone() вернет true , и мы увидим на консоли следующее:

Задачи жестко связаны с сервисом исполнителей, и, если вы его остановите, попытка получить результат задачи выбросит исключение:

Вы, возможно, заметили, что на этот раз мы создаем сервис немного по-другому: с помощью метода newFixedThreadPool(1) , который вернет исполнителя с пулом в один поток. Это эквивалентно вызову метода newSingleThreadExecutor() , однако мы можем изменить количество потоков в пуле.

Таймауты

Любой вызов метода future.get() блокирует поток до тех пор, пока задача не будет завершена. В наихудшем случае выполнение задачи не завершится никогда, блокируя ваше приложение. Избежать этого можно, передав таймаут:

Выполнение этого кода вызовет TimeoutException :

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

InvokeAll

Исполнители могут принимать список задач на выполнение с помощью метода invokeAll() , который принимает коллекцию callable-задач и возвращает список из Future .

В этом примере мы использовали функциональные потоки Java 8 для обработки задач, возвращенных методом invokeAll . Мы прошлись по всем задачам и вывели их результат на консоль. Если вы не знакомы с потоками (streams) Java 8, смотрите мое руководство.

InvokeAny

Другой способ отдать на выполнение несколько задач — метод invokeAny() . Он работает немного по-другому: вместо возврата Future он блокирует поток до того, как завершится хоть одна задача, и возвращает ее результат.

Чтобы показать, как работает этот метод, создадим метод, эмулирующий поведение различных задач. Он будет возвращать Callable , который вернет указанную строку после необходимой задержки:

Используем этот метод, чтобы создать несколько задач с разными строками и задержками от одной до трех секунд. Отправка этих задач исполнителю через метод invokeAny() вернет результат задачи с наименьшей задержкой. В данном случае это «task2»:

Читать еще:  Java распознавание изображений

В примере выше использован еще один вид исполнителей, который создается с помощью метода newWorkStealingPool() . Этот метод появился в Java 8 и ведет себя не так, как другие: вместо использования фиксированного количества потоков он создает ForkJoinPool с определенным параллелизмом (parallelism size), по умолчанию равным количеству ядер машины.

ForkJoinPool впервые появился в Java 7, и мы рассмотрим его подробнее в следующих частях нашего руководства. А теперь давайте посмотрим на исполнители с планировщиком (scheduled executors).

Исполнители с планировщиком

Мы уже знаем, как отдать задачу исполнителю и получить ее результат. Для того, чтобы периодически запускать задачу, мы можем использовать пул потоков с планировщиком.

ScheduledExecutorService способен запускать задачи один или несколько раз с заданным интервалом.

Этот пример показывает, как заставить исполнитель выполнить задачу через три секунды:

Когда мы передаем задачу планировщику, он возвращает особый тип Future — ScheduledFuture , который предоставляет метод getDelay() для получения оставшегося до запуска времени.

У исполнителя с планировщиком есть два метода для установки задач: scheduleAtFixedRate() и scheduleWithFixedDelay() . Первый устанавливает задачи с определенным интервалом, например, в одну секунду:

Кроме того, он принимает начальную задержку, которая определяет время до первого запуска.

Обратите внимание, что метод scheduleAtFixedRate() не берет в расчет время выполнения задачи. Так, если вы поставите задачу, которая выполняется две секунды, с интервалом в одну, пул потоков рано или поздно переполнится.

В этом случае необходимо использовать метод scheduleWithFixedDelay() . Он работает примерно так же, как и предыдущий, но указанный интервал будет отсчитываться от времени завершения предыдущей задачи.

В этом примере мы ставим задачу с задержкой в одну секунду между окончанием выполнения задачи и началом следующей. Начальной задержки нет, и каждая задача выполняется две секунды. Так, задачи будут запускаться на 0, 3, 6, 9 и т. д. секунде. Как видите, метод scheduleWithFixedDelay() весьма полезен, если мы не можем заранее сказать, сколько будет выполняться задача.

Это была первая часть серии статей про многопоточное программирование. Настоятельно рекомендую разобрать вышеприведенные примеры самостоятельно. Все они доступны на GitHub. Можете смело форкать репозиторий и добавлять его в избранное.

Надеюсь, вам понравилась статья. Если у вас возникли какие-либо вопросы, вы можете задать их в твиттере.

Пять вещей, которые вы не знали о . пакете java.util.concurrent . Часть 1

Многопоточное программирование с параллельными коллекциями

Об этой серии статей

Вы думаете, что знаете, как программировать на Java? На самом деле большинство разработчиков лишь поверхностно знакомятся с платформой Java, узнавая лишь то, что необходимо для выполнения работы. В этом цикле, статей Тед Ньювард углубляется в основную функциональность платформы Java, чтобы рассказать о малоизвестных фактах, знание которых может помочь при решении самых сложных задач программирования.

Параллельные коллекции были огромным вкладом в Java™ 5, однако многие Java-разработчики упустили их из вида на фоне шумихи об аннотациях и родовых типах (generics). Кроме того (и, возможно, это будет более верным), многие разработчики избегают этого пакета, так как предполагают, что он, как и проблемы, которые он призван решать, обязательно должен быть сложным.

На самом деле, в пакете java.util.concurrent содержится множество классов, которые эффективно решают многие проблемы параллелизма, не утруждая при этом вас. Читайте дальше, чтобы узнать, как классы java.util.concurrent , такие как CopyOnWriteArrayList и BlockingQueue , помогают решать больные вопросы многопоточного программирования.

1. TimeUnit

Перечисление java.util.concurrent.TimeUnit хотя и не является коллекцией, тем не менее делает код гораздо более легким для чтения. Применение TimeUnit освобождает разработчиков, использующих ваш метод или API, от необходимости работать с миллисекундами.

TimeUnit включает в себя значения для всех единиц времени – от MILLISECONDS (миллисекунды) и MICROSECONDS (микросекунды) до DAYS (дни) и HOURS (часы), т.е. оно охватывает почти все интервалы времени, которые могут понадобиться разработчику. А благодаря имеющимся методам конвертации можно, например, легко пересчитать HOURS в MILLISECONDS .

2. CopyOnWriteArrayList

Создание копии массива – это слишком дорогая операция как по времени, так и по затратам ресурсов памяти. Поэтому вместо нее разработчики часто прибегают к использованию синхронизованной коллекции ArrayList . Однако это тоже затратный вариант, так как каждый раз при обходе содержимого коллекции для обеспечения целостности данных приходится синхронизировать все операции, в том числе чтение и запись.

Это неоправданно снижает производительность в сценариях, когда имеется множество операций чтения ArrayList , но мало операций изменения.

Эту проблему можно решить с помощью замечательной коллекции CopyOnWriteArrayList . Она определяется в Javadoc как «потокобезопасный вариант коллекции ArrayList , в которой все изменяющие операции (add, set и т.д.) реализованы посредством создания свежей копии массива».

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

Таким образом, коллекция CopyOnWriteArrayList идеальна именно для того сценария, в котором ArrayList нас подводит: коллекции с частым чтением и редкой записью, например слушатели ( Listener ) для события JavaBean .

3. BlockingQueue

Интерфейс BlockingQueue является очередью ( Queue ), т.е. его элементы хранятся в порядке «первый пришел, первый вышел» (FIFO – first in, first out). Элементы, вставленные в коллекцию в определенном порядке, будут извлечены из нее в том же самом порядке. Также интерфейс гарантирует, что любая попытка извлечь элемент из пустой очереди заблокирует вызывающий поток до тех пор, пока в коллекции не появится элемент, который можно извлечь. Аналогично, любая попытка вставить элемент в заполненную очередь заблокирует вызывающий поток, пока в коллекции не освободится место для нового элемента.

BlockingQueue изящно решает проблему передачи элементов, собранных одним потоком, для обработки в другой поток без явных хлопот о проблемах синхронизации. Хорошим примером является способ Guarded Blocks из официального руководства Java. В нем создается ограниченный буфер с одним слотом, после чего потоки, используя ручную синхронизацию и методы wait() / notifyAll() , сигнализируют друг другу, когда в слоте имеется новый элемент для обработки и когда слот готов к помещению в него нового элемента (cм. подробности в реализации Guarded Blocks).

Хотя код из руководства Guarded Blocks работает, он выглядит длинным, путаным и не вполне интуитивно понятным. В начале развития платформы Java разработчикам приходилось иметь дело с подобным кодом, однако сейчас 2010 год – наверняка положение дел улучшилось?

В листинге 1 показана переписанная версия кода Guarded Blocks , где я использую ArrayBlockingQueue вместо написанного вручную типа Drop .

Читать еще:  Stringbuilder append java
Листинг 1. BlockingQueue

Интерфейс ArrayBlockingQueue также уважает «честность», т.е. он может давать потокам чтения и записи доступ согласно принципу «первый пришел, первый вышел». Альтернативой этому может быть более эффективная политика, в которой допускается риск простаивания некоторых потоков. (А именно, было бы более эффективным позволять потокам чтения выполняться в то время, когда другие потоки чтения владеют блокировкой, но в такой стратегии есть риск того, что постоянная вереница потоков чтения не позволит потокам записи когда-либо выполнить свою работу).

Опасайтесь ошибки!

Кстати, если вы заметили, что в реализации Guarded Blocks имеется огромная ошибка, – вы правы. Что бы случилось, если бы разработчик синхронизировал экземпляр класса Drop в методе main() ?

BlockingQueue также поддерживает методы, принимающие параметр time , обозначающий, как долго потоки должны пребывать в блокированном состоянии перед тем как вернуть управление, сигнализируя о том, что добавление или извлечение элемента не удалось. Это позволяет избежать неограниченного ожидания, которое может привести к катастрофе в рабочей системе, а также легко может «подвесить» систему и принудить к перезагрузке.

4. ConcurrentMap

В Map имеется неочевидная ошибка параллелизма, которая вводит в заблуждение многих не знающих о ней Java-разработчиков. Легким решением данной проблемы является ConcurrentMap .

Когда доступ к коллекции Map осуществляется из нескольких потоков, перед сохранением пары ключ–значение часто с помощью методов containsKey() или get() определяется, имеется ли уже данный ключ в коллекции. Однако даже при использовании synchronized Map какой-либо поток может «подкрасться» и в середине процесса перехватить управление коллекцией Map . Проблема в том, что блокировка захватывается в начале вызова метода get() , затем освобождается, после чего снова захватывается в вызове метода put() . В результате может возникнуть гоночная ситуация между двумя потоками, итог которой может быть разным в зависимости от того, какой поток получит управление первым.

Если два потока вызывают метод в один и тот же момент времени, для каждого из них будет выполнена проверка, и каждый из них выполнит метод put , в результате чего значение, сохраненное первым потоком, будет потеряно. К счастью, интерфейс ConcurrentMap поддерживает несколько дополнительных методов, спроектированных для выполнения двух операций под одной блокировкой: например, метод putIfAbsent() сначала выполняет проверку, а затем помещает новое значение, только если данный ключ еще не хранится Map .

5. SynchronousQueues

SynchronousQueue , согласно Javadoc, является довольно интересной сущностью.

Блокирующая очередь, в которой каждая операция добавления должна ждать соответствующей операции удаления в другом потоке и наоборот. Синхронная очередь не имеет никакой внутренней емкости, даже емкости в один элемент.

В сущности, SynchronousQueue – это еще одна реализация упомянутого выше интерфейса BlockingQueue . Она предоставляет необычайно легковесный способ обмена одиночными элементами между потоками посредством семантики блокировки, используемой в ArrayBlockingQueue . В листинге 2 я переписал код из листинга 1 с использованием SynchronousQueue вместо ArrayBlockingQueue .

Листинг 2. SynchronousQueue

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

На практике SynchronousQueue похожа на «каналы для рандеву», имеющиеся в таких языках, как Ada или CSP. Также в некоторых других средах, в том числе .NET, такие сущности известны как «объединения» (см. раздел Ресурсы).

Интерфейсы Callable и Future в Java

Интерфейс Java Callable(java.util.concurrent.Callable) представляет асинхронную задачу, которая может быть выполнена отдельным потоком. Например, можно передать объект Callable в Java ExecutorService, который затем выполнит его асинхронно. Метод call() вызывается для выполнения асинхронной задачи.

Интерфейс Callable довольно прост. Он содержит единственный метод с именем call().

Если задача выполняется асинхронно, результат обычно передается обратно через Java Future. Это тот случай, когда Callable передается в ExecutorService для одновременного выполнения.

Callable использует Generic для определения типа возвращаемого объекта. Класс Executors предоставляет полезные методы для выполнения Java Callable в пуле потоков. Поскольку вызываемые задачи выполняются параллельно, нам нужно дождаться возвращенного объекта.

Callable задачи возвращают объект java.util.concurrent.Future. Используя объект Java Future, мы можем узнать состояние задачи Callable и получить возвращенный объект. Он предоставляет метод get(), который может ожидать завершения Callable и затем возвращать результат.

Future предоставляет метод cancel() для отмены связанной задачи Callable. Существует версия метода get(), в которой мы можем указать время ожидания результата, поэтому полезно избегать блокировки текущего потока на более длительное время.

Существуют методы isDone() и isCancelled() для определения текущего состояния связанной вызываемой задачи.

Вот простой пример задачи с Callable, которая возвращает имя потока, выполняющего задачу через одну секунду. Мы используем платформу Executor для параллельного выполнения 100 задач и используем Java Future для получения результата представленных задач.

После того, как мы выполним вышеуказанную программу, вы заметите задержку вывода, потому что метод get() ожидает завершения задачи, вызываемой Java. Также обратите внимание, что есть только 10 потоков, выполняющих эти задачи.

Вот фрагмент вывода вышеуказанной программы.

Mon Dec 31 20:40:15 PST 2012::pool-1-thread-1
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-3
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-4
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-5
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-6
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-7
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-8
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-9
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-10
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
.

Что делать, если мы хотим переопределить некоторые методы, например, переопределить метод get() для тайм-аута через некоторое время по умолчанию, а не ждать бесконечно?

В этом случае пригодится класс Java FutureTask, который является базовой реализацией Future.

Callable против Runnable

Callable похож на Runnable в том, что оба они представляют задачу, которая предназначена для одновременного выполнения отдельным потоком.

Callable отличается от Runnable тем, что метод run() не возвращает значение и не может генерировать исключения (только RuntimeExceptions).

Runnable изначально был разработан для длительного параллельного выполнения, например, одновременный запуск сетевого сервера или просмотр каталога на наличие новых файлов. Интерфейс Callable больше предназначен для одноразовых задач, которые возвращают один результат.

Средняя оценка / 5. Количество голосов:

Спасибо, помогите другим — напишите комментарий, добавьте информации к статье.

Или поделись статьей

Видим, что вы не нашли ответ на свой вопрос.

Читать еще:  Виртуальные функции java

Java util concurrent

Implementations. Classes ThreadPoolExecutor and ScheduledThreadPoolExecutor provide tunable, flexible thread pools. The Executors class provides factory methods for the most common kinds and configurations of Executors, as well as a few utility methods for using them. Other utilities based on Executors include the concrete class FutureTask providing a common extensible implementation of Futures, and ExecutorCompletionService , that assists in coordinating the processing of groups of asynchronous tasks.

Class ForkJoinPool provides an Executor primarily designed for processing instances of ForkJoinTask and its subclasses. These classes employ a work-stealing scheduler that attains high throughput for tasks conforming to restrictions that often hold in computation-intensive parallel processing.

Queues

Five implementations in java.util.concurrent support the extended BlockingQueue interface, that defines blocking versions of put and take: LinkedBlockingQueue , ArrayBlockingQueue , SynchronousQueue , PriorityBlockingQueue , and DelayQueue . The different classes cover the most common usage contexts for producer-consumer, messaging, parallel tasking, and related concurrent designs.

Extended interface TransferQueue , and implementation LinkedTransferQueue introduce a synchronous transfer method (along with related features) in which a producer may optionally block awaiting its consumer.

The BlockingDeque interface extends BlockingQueue to support both FIFO and LIFO (stack-based) operations. Class LinkedBlockingDeque provides an implementation.

Timing

Synchronizers

Concurrent Collections

The «Concurrent» prefix used with some classes in this package is a shorthand indicating several differences from similar «synchronized» classes. For example java.util.Hashtable and Collections.synchronizedMap(new HashMap()) are synchronized. But ConcurrentHashMap is «concurrent». A concurrent collection is thread-safe, but not governed by a single exclusion lock. In the particular case of ConcurrentHashMap, it safely permits any number of concurrent reads as well as a tunable number of concurrent writes. «Synchronized» classes can be useful when you need to prevent all access to a collection via a single lock, at the expense of poorer scalability. In other cases in which multiple threads are expected to access a common collection, «concurrent» versions are normally preferable. And unsynchronized collections are preferable when either collections are unshared, or are accessible only when holding other locks.

Most concurrent Collection implementations (including most Queues) also differ from the usual java.util conventions in that their Iterators provide weakly consistent rather than fast-fail traversal. A weakly consistent iterator is thread-safe, but does not necessarily freeze the collection while iterating, so it may (or may not) reflect any updates since the iterator was created.

Memory Consistency Properties

  • Overview
  • Package
  • Class
  • Use
  • Tree
  • Deprecated
  • Index
  • Help

Submit a bug or feature
For further API reference and developer documentation, see Java SE Documentation. That documentation contains more detailed, developer-targeted descriptions, with conceptual overviews, definitions of terms, workarounds, and working code examples.
Copyright © 1993, 2015, Oracle and/or its affiliates. All rights reserved.

Java.util.concurrent.Phaser класс в Java с примерами

Основное назначение Phaser — включить синхронизацию потоков, представляющих одну или несколько фаз активности. Это позволяет нам определить объект синхронизации, который ожидает завершения определенной фазы. Затем он переходит к следующему этапу, пока этот этап не завершится. Он также может использоваться для синхронизации одной фазы, и в этом отношении он действует как CyclicBarrier.

Классовая иерархия

Синтаксис

Конструкторы:

  1. Phaser () — создает фазер с изначально нулевыми зарегистрированными сторонами. Поток может использовать этот фазер только после его регистрации.
  2. Фазер (целые группы) — это создает фазер, который требует количество потоков сторон для перехода к следующей фазе.
  3. Phaser (Phaser parent) — указывает родительский фазер для нового объекта. Количество зарегистрированных партий установлено равным нулю.
  4. Phaser (Phaser parent, int party). Здесь указывается родительский Phaser для вновь созданного объекта и количество сторон, необходимых для перехода к следующему этапу.

Пример:
Примечание . Выходные данные могут отличаться при каждом запуске.

// Java-программа для отображения класса Phaser

// Поток выполнения, использующий фазер.

class MyThread implements Runnable <

public MyThread(Phaser phaser,

this .phaser = phaser;

this .title = title;

new Thread( this ).start();

public void run()

System.out.println( «Thread: » + title

+ «nPhase One Started» );

// Остановить выполнение, чтобы предотвратить беспорядочный вывод

catch (InterruptedException e) <

System.out.println( «Thread: » + title

+ «nPhase Two Started» );

// Остановить выполнение, чтобы предотвратить беспорядочный вывод

catch (InterruptedException e) <

System.out.println( «Thread: » + title

+ «nPhase Three Started» );

public class PhaserExample <

public static void main(String[] args)

Phaser phaser = new Phaser();

new MyThread(phaser, «A» );

new MyThread(phaser, «B» );

new MyThread(phaser, «C» );

// Ожидание завершения всех потоков первой фазы.

System.out.println( «Phase One Ended» );

// Дождемся, пока все потоки завершат второй этап.

System.out.println( «Phase Two Ended» );

System.out.println( «Phase Three Ended» );

// Отмена регистрации основного потока.

System.out.println( «Phaser is terminated» );

Методы:

  1. int register () — Этот метод используется для регистрации партий после создания фазера. Возвращает номер фазы фазы, на которой он зарегистрирован.
  2. int receive () — Этот метод сигнализирует, что поток завершил некоторую часть задачи. Он не приостанавливает выполнение вызывающего потока. Возвращает текущий номер фазы или отрицательное значение, если фазер был прерван.
  3. int receiveAndDeregister () — Этот метод позволяет потоку прийти к фазе и отменить регистрацию, не дожидаясь прибытия других потоков. Возвращает текущий номер фазы или отрицательное значение, если фазер был прерван.
  4. int receiveAndAwaitAdvance () — Этот метод приостанавливает выполнение потока на этапе, ожидая других потоков. Возвращает текущий номер фазы или отрицательное значение, если фазер был прерван.
  5. final int getPhase () — Этот метод возвращает номер текущей фазы. Отрицательное значение возвращается, если вызов фазировщиков завершен.
  6. boolean onAdvance (int phase, int party) — этот метод помогает определить, как должно происходить продвижение фазы. Для этого пользователь должен переопределить этот метод. Чтобы завершить фазер, метод onAdvance () возвращает true, в противном случае возвращает false;

Пример, демонстрирующий методы класса Phaser — метод переопределяется так, что Phaser выполняет только указанное количество фаз.

// Java-программа для демонстрации
// методы класса Phaser

// Расширяем MyPhaser и переопределяем onAdvance ()
// так что только определенное количество фаз
// выполнены

class MyPhaser extends Phaser <

MyPhaser( int parties, int phaseCount)

numPhases = phaseCount — 1 ;

protected boolean onAdvance( int phase,

// Если все фазы завершены, вернуть true.

if (phase == numPhases

// в противном случае возвращаем false

// Поток выполнения, использующий фазер

Ссылка на основную публикацию
ВсеИнструменты 220 Вольт
Adblock
detector
×
×