Java. Полное руководство. 8-е издание. С. Н. Тригуб Перевод с английского и редакция
Скачать 25.04 Mb.
|
if(p == numPhases II regParties == 0) return Как можно заметить, для достижения желаемого результата необходимо совсем немного кода. Перед завершением темы имеет смысл указать, что вы не обязаны явно расширять класс Phaser. Как ив предыдущем примере, достаточно переопределить метод onAdvance () . В некоторых случаях может быть создан более компактный код при использовании анонимного внутреннего класса, переопределяющего метод У класса Phaser есть дополнительные возможности, которые могут быть полезны в ваших приложениях. Вы можете ожидать специфической фазы при вызове метода await Advance () , как показано здесь awaitAdvance(int фаза) Здесь параметр фаза указывает номер фазы, на которой будет ожидать метод await Advance () , пока не произойдет переход к следующей фазе. Он завершится немедленно, если аргумент, переданный в параметре фазане будет равен текущей фазе. Он также завершится немедленно, если фазер завершит работу. Однако если в параметре фаза будет передана текущая фаза, то он будет ждать до инкремента фазы 8 9 Часть II. Библиотека Этот метод должен быть вызван только зарегистрированной стороной. Есть также прерывающая версия этого метода по имени awaitAdvancelnterrupt ibly (Чтобы зарегистрировать несколько сторон, вызовите метод bulkRegister ( Чтобы получить количество зарегистрированных сторон, вызовите метод get- RegisteredParties () . Вы можете также получить количество сторон, завершивших и не завершивших работу, при вызове методов getArrivedParties () и getUnarrivedParties () соответственно. Чтобы перевести фазер в завершенное состояние, вызовите метод f orceTermination ( Класс Phaser позволяет также создать дерево фазеров. Он снабжен двумя дополнительными конструкторами, которые позволяют вам определять родителя и метод getParent (Использование исполнителя Параллельный API поддерживает функциональную возможность, называемую исполнителем (executor), которая создает потоки и управляет ими. По сути, исполнитель предлагает альтернативный вариант управления потоками с помощью класса В основе исполнителя лежит интерфейс Executor, который определяет следующий метод execute(Runnable поток biib)biВыполняется поток, указанный в параметре поток bii.iТаким образом, метод ex ecute () запускает заданный поток. Интерфейс ExecutorService расширяет интерфейс Executor за счет добавления методов, помогающих управлять выполнением потоков и контролировать его. Например, интерфейс ExecutorService определяет метод shutdown(), показанный ниже, который останавливает все потоки, находящиеся в данный момент под управлением экземпляра интерфейса ExecutorService. void Интерфейс ExecutorService также определяет методы, которые запускают потоки, возвращающие результаты, выполняют совокупность потоков и определяют состояние останова. Некоторые из этих методов мы рассмотрим чуть позже. Определяется также и интерфейс ScheduledExecutorService, который расширяет интерфейс ExecutorService для поддержки планирования потоков. В параллельном API имеется три предварительно определенных класса исполнителей ThreadPoolExecutor, ScheduledThreadPoolExecutor и Fork JoinPool. Класс ThreadPoolExecutor реализует интерфейсы Executor и ExecutorService и обеспечивает поддержку управляемого пула потоков. Класс ScheduledThreadPoolExecutor тоже реализует интерфейс Scheduled ExecutorService для поддержки возможности планирования пула потоков. Класс ForkJoinPool реализует интерфейсы Executor ион используется инфраструктурой Fork/Join Framework и рассматривается далее в этой главе. Пул потоков предлагает набор потоков для решения разнообразных задач. Вместо того чтобы каждая задача имела дело со своим собственным потоком, используются потоки из пула. Это позволяет сократить нагрузку, связанную с созданием множества отдельных потоков. Хотя классы ThreadPoolExecutor и ScheduledThreadPoolExecutor можно использовать напрямую, чаще всего вам будет необходимо получать исполнителя при помощи вызова одного из следующих статических фабричных методов, определенных во вспомогательном классе Executors. Далее показаны некоторые примеры Глава 27. Параллельные утилиты 8 9 1 static ExecutorService newCachedThreadPool() static ExecutorService newFixedThreadPool(int количПотоков) static ScheduledExecutorService newScheduledThreadPool(int количПотоков) Метод n e w C a c h e d T h r e a d P o o l () создает пул потоков, который не только добавляет потоки по мере необходимости, но и по возможности повторно их использует. Метод n e w F ix e d T h r e a d P o o l () создает пул потоков, состоящий из определенного количества потоков. Метод n e w S c h e d u l e d T h r e a d P o o l () создает пул потоков, в котором можно осуществлять планирование потоков. Каждый из них возвращает ссылку на интерфейс E x e c u t o r S e r v i c e , который можно использовать для управления пулом. Простой пример исполнителя Прежде чем продолжить обсуждение, давайте рассмотрим простой пример применения исполнителя. В следующей программе создается фиксированный пул, содержащий два потока. Затем этот пул используется для выполнения четырех задач. Таким образом, четыре задачи разделяют два потока, находящихся в пуле. После того как задачи будут выполнены, пул закрывается и программа завершает выполнение Простой пример, в котором используется исполнитель java.util.concurrent.*; class SimpExec { public static void main(String a r g s []) { CountDownLatch cdl = new CountDownLatch(5); CountDownLatch cdl2 = new CountDownLatch(5); CountDownLatch cdl3 = new CountDownLatch(5); CountDownLatch cdl4 = new CountDownLatch(5); ExecutorService es = Запуск Начало потоков s .execute(new MyThread(cdl, "A")); e s .execute(new M yThread(cdl2, "B")); e s .execute(new MyThread(cdl3, "C")); e s .execute(new M yThread(cdl4, "D")); try { c d l .a w a i t (); c d l 2 .a w a i t (); c d l 3 .a w a i t (); c d l 4 .a w a i t (); } catch (InterruptedException exc) { System.out.println(exc); } e s Завершение MyThread implements Runnable { String name; CountDownLatch latch; MyThread(CountDownLatch c, String n) { latch = c; 8 9 Часть II. Библиотека Java name = n; new Thread(this); } public void r u n () { for(int i = 0; i < 5; i++) { System.out.println(name + ": " + Ниже показаны результаты выполнения программы. Запуск А 0 А 1 А 2 А 3 АСС С 2 С 3 СВ В 1 В 2 В 3 В 4 Завершение Судя по результатам, несмотря на то что в пуле содержится всего два потока, выполняются все четыре задачи. Однако только две из них могут быть выполнены одновременно. Остальные должны ожидать, пока один из потоков пула не освободится, после чего его можно будет использовать. Вызов метода sh u td o w n () очень важен. Если бы его не было в программе, она не смогла бы завершиться, поскольку исполнитель оставался бы активным. Убедиться в этом можно, закомментировав вызов метода sh u td o w n () и посмотрев, что из этого получится. Использование интерфейсов C a l l a b l e и F u t u r Одним из наиболее важных и, без сомнений, ярких средств в параллельном API является интерфейс C a l l a b l e . Он представляет поток, возвращающий значение. Приложение может использовать объекты интерфейса C a l l a b l e для вычисления результатов, которые затем будут возвращены вызывающему потоку. Это мощный механизм, поскольку он облегчает написание кода для множества различных числовых расчетов, в которых частичные результаты вычисляются одновременно. Его также можно использовать и для запуска потока, возвращающего код состояния, который свидетельствует об успешном выполнении потока Глава 27. Параллельные утилиты 8 9 Интерфейс является обобщенным интерфейсом, который определяется следующим образом Здесь параметр задает тип данных, возвращаемых задачей. Интерфейс C a l l a b l e определяет только один метод c a l l () . V c a l l () throws Внутри метода c a l l ( ) определяется задача, которую требуется выполнить. После того как она будет выполнена, возвращается результат. Если результат невозможно вычислить, метод c a l l () передает исключение. Задача интерфейса C a l l a b l e решается при вызове метода s u b m i t ( ) , определенного в интерфейсе E x e c u t o r S e r v i c e . Метод s u b m i t () может иметь три формы, однако для интерфейса используется только одна из них. <Т> Future задача) Здесь параметр задача представляет объект интерфейса C a l l a b l e , который будет выполняться в собственном потоке. Результат возвращается через объект интерфейса F u t u r e Интерфейс F u t u r e является обобщенным интерфейсом, представляющим значение, которое будет возвращено при помощи объекта интерфейса C a l l a b l e . Поскольку это значение будет получено в некотором будущем, то имя интерфейса ( F u t u r e ) говорит само за себя. Интерфейс определяется следующим образом Здесь параметр определяет тип результата. Чтобы получить значение, нужно вызвать метод g e t () интерфейса F u t u r e , который имеет следующие две формы g e t () throws InterruptedException, ExecutionException V get(long ожидать TimeUnit tu) throws InterruptedException, ExecutionException, В первом случае ожидание получения результатов длится бесконечно долго. Во втором случае можно указать период времени в параметре ожидать Единицы периода времени в этом параметре задаются параметром tu , который представляет собой объект перечисления T im e U n it, рассматриваемый далее в этой главе. В следующей программе показан пример применения интерфейсов C a l l a b l e и F u t u r e . В ней будут созданы три задачи, выполняющие три разных вычисления. Первая задача возвращает суммарное значение, вторая находит длину гипотенузы прямоугольного треугольника с известными значениями длин его сторона третья определяет факториал для заданного значения. Все три вычисления производятся одновременно Пример, в котором используется Callable. import java.util.concurrent. *; class CallableDemo { public static void main(String a r g s []) { ExecutorService es = Executors.newFixedThreadPool(3); Future Future Future 8 9 4 Часть II. Библиотека Java f = e s .submit(new Sum(lO)); f2 = e s .submit(new H y p o t (3, 4)); f3 = e s .submit(new Factorial(5)); try { System.out.println(f.g e t ()) ; System.out.println(f2 .get()); System.out.println(f3.g e t () ) ; } catch (InterruptedException exc) { System.out.println(exc); } catch (ExecutionException exc) { System.out.println(exc); } e s Завершение Три потока вычислений class Sum implements Callable int stop; Sum (int v) { stop = v; } public Integer c a l l () { int sum = 0; for(int i = 1; i <= stop; i++) { sum += i; } return sum; } } class Hypot implements Callable double sidel, side2; Hypot(double si, double s2) { s i de 1 = s 1 ; side2 = s2; } public Double call() { return M a t h .s q r t ((sidel*sidel) + (side2*side2)) } } class Factorial implements Callable int stop; Factorial(int v) { stop = v; } public Integer call() { int fact = 1; for(int i = 2; i <= stop; i++) { fact *= i; } return fact; } Запуск } Ниже приводятся результаты выполнения программы Глава 27. Параллельные утилиты 8 9 5 55 5 .0 120 Завершение Перечисление T i m e U n i Параллельный API определяет методы, принимающие параметр типа TimeUnit, который служит для определения периода времени. Перечисление TimeUnit используется для определения временного разбиения (или разрешения. Оно определено в пакете java.util. concurrent и может принимать одно из следующих значений. • DAYS • HOURS • MINUTES • SECONDS • MICROSECONDS • MILLISECONDS • NANOSECONDS Несмотря на то что с помощью перечисления TimeUnit можно определить любое из этих значений в вызовах методов, принимающих параметр синхронизации, нет гарантии того, что система сможет работать с заданным разрешением. Ниже следует пример, в котором используется перечисление TimeUnit. Класс CallableDemo, показанный в предыдущем разделе, был изменен, чтобы использовать вторую форму метода get () , принимающего параметр типа TimeUnit. try { S y s t e m .o u t .p r i n t l n (f .g e t (10, Tim eUnit.M ILLISECONDS)); System .out. p r i n t l n (f 2 . g e t (10, Tim eUnit.M ILLISECONDS)); S y s t e m .o u t.p r in t ln ( f 3 . g e t ( 10 , Tim eUnit. MILLISECONDS)); } catch (InterruptedException exc) { System .out.p r i n t I n (e x c ); }catch (ExecutionException exc) { S y s t e m .o u t.p r in t ln (e x c ); } catch (TimeoutException exc) { S y s t e m .o u t .p r in t ln (e x c В этом варианте ни один из вызовов метода get () не будет ожидать дольше 10 миллисекунд. Перечисление TimeUnit определяет различные методы, выполняющие преобразование единиц convert (long Сзнач- , TimeUnit tu) long toM icros(long t 3Ha4) long t o M i l l i s (long Сзяач) long toNanos(long t3Ha4) long toSeconds(long Сзяач) long toDays(long t3Ha4) long toHours(long Сзнач) long toM inutes(long Метод convert () преобразует t знач в определенные единицы и возвращает результат. Методы toXXX выполняют указанное преобразование и возвращают результат 8 9 Часть II. Библиотека Перечисление T im e U n it также определяет следующие методы синхронизации sleep(long задержка InterruptedExecution void timedJoin(Thread поток задержка InterruptedExecution void timedWait(Object объект задержка Метод s l e e p () приостанавливает выполнение на определенный период времени, который задается в виде вызывающей константы перечисления. Он преобразуется в вызов метода T h r e a d . s l e e p (Метод t i m e d J o i n () является специализированной версией метода T h r e a d , j o i n ( ) , в котором поток (параметр поток приостанавливается на период времени, указанный параметром задержка. Метод ti m e d W a it () является специализированной версией метода Obj e c t w a i t (), в котором объект ожидает период времени, заданный параметром задержка, который исчисляется в вызывающих единицах времени. Параллельные коллекции Как уже отмечалось, параллельный API определяет несколько коллекций, которые были разработаны для выполнения параллельных операций. К ним относятся следующие коллекции r r a y B l o c k i n g Q u e u e • C o n c u r г e n tH a sh M a p • C o n c u r r e n tL in k e d D e q u e Добавлено в JDK 7) • C o n c u r r e n t L in k e d Q u e u e • C o n c u r r e n t S k i p L i s t M a p • C o n c u r r e n t S k i p L i s t S e t • C o p y O n W r ite A r r a y L is t • C o p y O n W r ite A r r a y S e t • D e la y Q u e u e • L in k e d B lo c k in g D e q u e • L in k e d B lo c k in g Q u e u e • L i n k e d T r a n s f e r Q u e u e Добавлено в JDK 7) • P r i o r i t y B l o c k i n g Q u e u e • S y n c h ro n o u s Q u e u Они предлагают параллельные альтернативы связанным сними классам, определенным в инфраструктуре Collections Framework. Эти коллекции работают подобно другим коллекциям, за исключением того, что они поддерживают параллелизм. Программисты, знакомые с инфраструктурой Collections Framework, не будут иметь проблем с использованием этих параллельных коллекций. Блокировки Пакет j a v a . u t i l . c o n c u r r e n t . l o c k s предоставляет поддержку блокировок (lock), которые являются объектами, предлагающими альтернативу использованию блоков s y n c h r o n i z e d для управления доступом к общему ресурсу. Давайте разберемся стем, как работают блокировки. Прежде чем получить доступ к общему ре Глава 27. Параллельные утилиты 9 7 сурсу, запрашивается блокировка, защищающая этот ресурс. Когда доступ к ресурсу будет завершен, блокировка снимается. Если второй поток попытается запросить блокировку в тот момент, когда она используется еще каким-нибудь потоком, первый поток будет ожидать, пока блокировка не будет снята. Благодаря этому появляется возможность избежать возникновения конфликта доступа к общему ресурсу Блокировки особенно полезны тогда, когда нескольким потокам нужно получить доступ к значению из общих данных. Например, приложение складского учета может иметь поток, который сначала подтверждает, что товар имеется на складе, а затем уменьшает количество доступных товаров после каждой продажи. Если будет выполняться два или более таких потока, то без синхронизации может получиться так, что один поток начнет свою транзакцию в момент выполнения транзакции другим потоком. В результате этого оба потока будут предполагать о существовании достаточного количества товара, хотя на самом деле товара будет ровно столько, сколько требуется для осуществления одной продажи. В подобных ситуациях с помощью блокировок можно организовывать синхронную работу потоков. Каждая блокировка реализует интерфейс Lock. В табл. 27.2 перечислены методы, определенные интерфейсом Lock. В общем случае для запроса блокировки необходимо вызвать метод lo c k (). Если блокировка не будет доступна, метод lo c k () войдет в режим ожидания. Для снятия блокировки вызовите метод u n l o c k (). Чтобы узнать, является ли блокировка свободной, и запросить ее, если она свободна, вызовите метод t r y Lock (). Метод не будет ожидать блокировку, если она не является доступной. Наоборот, он возвращает значение t r u e , если блокировка получена, и значение f a l s e — если нет. Метод n e w C o n d itio n () возвращает объект C o n d i t io n , связанный с блокировкой. Применение C o n d i t io n позволяет расширить возможности управления блокировками с помощью методов a w a i t () и s i g n a l ( ), которые обеспечивают функциональные возможности, подобные тем, что предлагаются методами Obj e c t . w a i t () и Obj e c t . n o t i f y (Таблица 27.2. Методы интерфейса Г '• ........ .................. - Y- - -------------- : ------------------------- --------------------------- Метод i;f^ r ^ ^ ^ Описание - . t -.- * Ожидание длится до тех пор, пока вызываемая блокировка не может быть получена Ожидание длится до тех пор, пока вызываемая блокировка не может быть получена, если только не произойдет прерывание Возвращает объект C o n ditio n, связанный с вызываемой блокировкой Пытается запросить блокировку. Этот метод не входит в режим ожидания, если блокировка не является свободной. Вместо этого он возвращает значение tr u e , если блокировка была получена, и значение f a ls e , если на данный момент блокировка используется другим потоком Пытается получить блокировку. Если блокировка недоступна, то метод будет ожидать столько времени, сколько указано в параметре ожидать, единицы которого определены параметром tu. Он возвращает значение tru e , если блокировка была получена, и значение f a l s e , если блокировка не была получена в течение заданного периода Снимает блокировку Зак 3030 |