Java. Полное руководство. 8-е издание. С. Н. Тригуб Перевод с английского и редакция
Скачать 25.04 Mb.
|
V exchange(V буфер long ожидать TimeUnit tu) throws InterruptedException, Здесь параметр буфер представляет ссылку на данные для обмена. Данные, полученные из другого потока, возвращаются. Вторая форма метода exchange () 8 8 2 Часть II. Библиотека позволяет определить время простоя. Ключевая особенность метода e x c h a n g e () заключается в том, что его выполнение не будет продолжено до тех пор, пока он не будет вызван для одного итого же объекта класса E x c h a n g e r двумя отдельными потоками. Таким образом, метод e x c h a n g e () синхронизирует обмен данными. Ниже показан пример применения класса E x c h a n g er. В нем создается два потока. Один поток создает пустой буфер, который получает данные, занесенные в него другим потоком. Таким образом, первый поток меняет пустой поток на полный Пример использования класса Exchanger. import java.util.concurrent.Exchanger; class ExgrDemo { public static void main(String a r g s []) { Exchanger new UseString(exgr); new MakeString(exgr); } } // Поток Thread, формирующий строку class MakeString implements Runnable { Exchanger String str; MakeString(Exchanger ex = c; str = new S t ringO; new Thread(this).start() ; } public void r u n () { char ch = 'A'; for(int i = 0; i < 3; i++) { // Заполнение буфера for(int j = 0; j < 5; j++) str += (char) ch++; try { // Заполненный буфер становится пустым str = e x .exchange(str); } c a tch(InterruptedException exc) { System.out.println(exc); } } } } // Поток Thread, использующий строку class UseString implements Runnable { Exchanger String str; UseString(Exchanger ex = с ; new T h read(this).start(); } public void r u n () { for(int i=0; i < 3; i++) { try { // Пустой буфер становится заполненным Глава 27. Параллельные утилиты 8 8 3 str = e x . exchange (new S t r i n g O ) ; S y s t e m . o u t . p r i n t l n Получено . o u t . p r i n t l n ( e x c ) Ниже показаны результаты выполнения этой программы. П о луче но Получено Получено В этой программе метод main () создает объект класса Exchanger для строк. Этот объект затем служит для синхронизации обмена строками между классами MakeString и UseString. Класс MakeString заполняет строку данными. Класс UseString заполняет пустой буфера затем отображает содержимое только что созданной строки. Замена пустого буфера полным синхронизируется с помощью метода exchange ( ) , который вызывается методом run () обоих классов. Класс Комплект JDK 7 содержит новый класс синхронизации Phaser. Его главная задача — обеспечение синхронизации потоков, которые представляют одну или несколько фаз действия. Например, у вас может быть ряд потоков, которые реализуют три фазы приложения обработки заказов. На первой фазе используются отдельные потоки, чтобы проверить информацию клиента, товар и его цену. По завершении этой фазы, у второй фазы есть два потока, которые вычисляют стоимость доставки и сумму соответствующего налога. Затем, на заключительной фазе, подтверждается оплата и определяется ориентировочное время доставки. В прошлом, чтобы синхронизировать несколько потоков, вовлеченных в этот сценарий, потребовалось бы выполнение некоторой работы с вашей стороны. С появлением класса Phaser процесс существенно упростился. Для начала имеет смысл узнать, что класс Phaser работает подобно описанному ранее классу CyclicBarrier, за исключением того, что он поддерживает несколько фаз. В результате класс Phaser позволяет определить объект синхронизации, который ожидает завершения определенной фазы. Затем он переходит к следующей фазе и снова ожидает ее завершения. Следует понять, что класс Phaser может также использоваться для синхронизации только одной фазы. В этом отношении он действует подобно классу CyclicBarrier. Однако его главная задача — синхронизация нескольких фаз. В классе Phaser определено четыре конструктора. В данном разделе используются следующие два h a s e r () P h a s e r (int количСторон) Первый конструктор создает фазер (phaser) с нулевым регистрационным счетом. Второй устанавливает значение регистрационного счета равным количСторон. К регистрируемым фазером объектам зачастую применяется термин сторона (party). Хотя обычно есть полное соответствие между количеством регистрантов и количеством синхронизируемых потоков, это необязательно. В обоих случаях текущая фаза нулевая. Таким образом, когда создается экземпляр класса Phaser, он первоначально находится в нулевой фазе 8 8 4 Часть II. Библиотека В общем, класс Phaser используется так. Сначала создается новый экземпляр класса Phaser. Затем на фазере регистрируется одна или несколько сторон вызовом метода register () или при указании необходимого количества сторон в конструкторе. Для каждой зарегистрированной стороны имеется фазер, ожидающий, пока все зарегистрированные стороны не закончат фазу. Сторона сообщает об этом при вызове одного из множества методов, предоставляемых классом Phaser, таких как метод arrive ( ) или arr iveAndAwa it Advance () . Как только все стороны готовы, фаза считается законченной и фазер может перейти к следующей фазе (если она есть) или завершить работу. Следующие разделы объясняют процесс подробнее. Для регистрации стороны после создания объекта класса Phaser вызовите метод register(). int В результате он возвратит номер зарегистрированной фазы. Чтобы сообщить о завершении фазы, сторона должна вызвать метод arrive () или некий его вариант. Когда количество завершений равняется количеству зарегистрированных сторон, фаза заканчивается и объект класса Phaser переходит к следующей фазе (если она есть. Метод arrive () имеет следующую общую форму Этот метод сообщает о том, что сторона (обычно поток выполнения) закончила некую задачу (или часть задачи. Он возвращает текущий номер фазы. Если работа фазера закончена, то он возвращает отрицательное значение. Метод ar rive () не приостанавливает выполнение вызывающего потока. Это значит, что он не ожидает завершения фазы. Этот метод должен быть вызван только зарегистрированной стороной. Если вы хотите указать на завершение фазы, а затем ожидать завершения этой фазы всеми остальными регистрантами, используйте метод arriveAndAwaitAd- vance(). int Он ожидает завершения всех сторон и возвращает номер следующей фазы или отрицательное значение, если фазер завершил работу. Этот метод должен быть вызван только зарегистрированной стороной. Поток может завершиться, а затем отменить свою регистрацию, вызвав метод arriveAndDeregister(). int Метод возвращает номер текущей фазы или отрицательное значение, если фа зер завершил работу. Он не ожидает завершения фазы. Этот метод должен быть вызван только зарегистрированной стороной. Чтобы получить номер текущей фазы, вызовите метод get Phase () , который выглядит так int Когда объект класса Phaser будет создан, первая фаза будет иметь номер 0, вторая — 1, третья — 2 и т.д. Если вызывающий объект класса Phas er завершил работу, возвращается отрицательное значение. Вот пример, демонстрирующий класс Phaser в действии. Здесь создается три потока, каждый из которых имеет три фазы. Для синхронизации каждой фазы используется класс Phaser. // Пример применения класса Phaser, import java.util.concurrent.*; Глава 27. Параллельные утилиты PhaserDemo { public static void main(String a r g s []) { Phaser phsr = new Phaser(1); int curPhase; System.out.println("Starting"); new MyThread(phsr, "A"); new MyThread(phsr, "B"); new MyThread(phsr, "C"); // Ожидать завершения всеми потоками фазы один curPhase = p h s r .getPhase(); p h s r .arriveAndAwaitAdvance(); System.out.println("Phase " + curPhase + " Complete"); // Ожидать завершения всеми потоками фазы два curPhase = p h s r .getPhase(); p h s r .arriveAndAwaitAdvance(); System.out.println("Phase " + curPhase + " Complete"); curPhase = p h s r .getPhase(); p h s r .arriveAndAwaitAdvance(); System.out.println("Phase " + curPhase + " Complete"); // Отменить регистрацию основного потока ph s r .arriveAndDeregister(); if(phsr.isTerminated()) System.out.println("The Phaser is terminated"); } } // Поток выполнения, использующий Phaser, class MyThread implements Runnable { Phaser phsr; String name; MyThread(Phaser p, String n) { phsr = p; name = n; p h s r .register() ; new Thread(this).st art(); } public void run() { System.out.println("Thread " + name + " Beginning Phase One") p h s r .arriveAndAwaitAdvance(); // Сигнал завершения Небольшая пауза для предотвращения перемешанного вывода Только для иллюстрации. Это необязательно для правильной // работы фазера. try { Thread.sleep(10); } c a t c h (InterruptedException e) { System.out.println(e); } System.out.println("Thread " + name + " Beginning Phase Two") p h s r .arriveAndAwaitAdvance(); // Сигнал завершения Часть II. Библиотека Java // Небольшая.пауза для предотвращения перемешанного вывода // Только для иллюстрации. Это необязательно для правильной // работы фазера. try ее рее Сигнал завершения и отмены регистрации. Вот вывод A Beginning Phase One Thread СВ В Beginning Phase Two Thread С Beginning Phase Two Thread A Beginning Phase Two Phase 1 Complete Thread СВ Давайте внимательнее рассмотрим ключевые разделы программы. Сначала в методе main () создается объект phsr класса Phaser с начальным номером стороны 1 (что соответствует основному потоку. Затем запускается три потока при создании трех объектов класса MyThread. Обратите внимание на то, что объекту класса MyThread передается ссылка на объект phsr (фазер). Объекты класса MyThread используют этот фазер для синхронизации своих действий. Затем метод main () вызывает метод get Phase (), чтобы получить номер текущей фазы (который первоначально является нулевым, а потом метод ar г (). Это приостанавливает метод main () , пока не закончится нулевая фаза, а этого не случится, пока все объекты класса MyThread также не вызовут метод arr iveAndAwa it Ad vance (). Когда это произойдет, метод main () возобновит выполнение, сообщив о завершении нулевой фазы, и перейдет ко второй фазе. Этот процесс повторяется до завершения всех трех фаз. Затем метод main () вызывает метод arriveAnd Deregister () . В этот момент регистрация всех трех объектов класса MyThread отменена. Поскольку это приводит к тому, что при переходе фазера к следующей фазе нет никаких зарегистрированных сторон, работа фазера заканчивается. Теперь рассмотрим объект класса MyThread. В первую очередь обратите внимание на то, что конструктору передается ссылка на фазер, который будет использована затем регистраторы с новым потоком как сторона этого фазера. Таким образом, каждый новый объект класса MyThread становится стороной, зарегистрированной с переданным фазером. Обратите также внимание на то, что у каждого потока есть три фазы. В этом примере каждая фаза состоит из знакоместа, которое просто отображает имя потока и то, что он делает. Безусловно, в реальном коде поток выполнял бы более существенные действия. Между первыми двумя фазами поток вызывает метод arr iveAndAwa it Advance () . Таким образом Глава 27. Параллельные утилиты 8 8 каждый поток ожидает завершения фазы всеми потоками (включая основной поток. После завершения всех потоков (включая основной поток) фазер переходит к следующей фазе. После третьей фазы каждый поток отменяет свою регистрацию вызовом метода arriveAndDeregister () . Как объясняют комментарии объекта класса MyThread, вызов метода sleep () используется в иллюстративных целях для предотвращения перемешивания вывода из-за многопоточности. Это необязательно для правильной работы фазера. Если вы удалите вызовы метода sleep () , то вывод может выглядеть немного перепутанным, но фазы все равно будут синхронизированы правильно. Еще один момент хотя в приведенном примере использовано три потока одинакового типа, это необязательное требование. Каждая сторона, которая использует фазер, может быть индивидуальной и выполнять индивидуальные задачи. Происходящее при переходе к следующей фазе вполне можно взять под свой контроль. Для этого следует переопределить метод onAdvance () . Этот метод вызывается средой выполнения, когда фазер переходит от одной фазы к следующей r o t e c t e d b oolean o n A d v a n c e (int фаза int количСторон) Здесь параметр фаза будет содержать текущий номер фазы перед его инкрементом, а параметр количС торон количество зарегистрированных сторон. Для завершения работы фазера метод onAdvance () должен возвратить значение true. Чтобы поддерживать фазер в действии, метод onAdvance () должен возвращать значение false. Заданная по умолчанию версия метода onAdvance () возвращает значение true это завершает работу фазера), когда нет никаких зарегистрированных сторон. Как правило, ваше переопределение также должно следовать этой практике. Одна из причин переопределения метода onAdvance () заключается в том, чтобы позволить фазеру выполнить определенное количество фаза затем остановиться. Ниже приведен пример разновидности такого применения. Здесь создается класс по имени My Phaser, который расширяет класс Phaser так, чтобы он выполнял определенное количество фаз. Для этого переопределяется метод onAdvance () . Конструктор класса My Phaser получает один аргумент, который определяет количество выполняемых фаз. Обратите внимание на то, что класс My Phase г автоматически регистрирует одну сторону. В данном примере это поведение полезно, но ваши приложения могут иметь другие потребности Расширение класса Phaser и переопределение метода o n A d v a n c e () так чтобы было выполнено только определенное количество фаз j a v a . u t i l .c o n c u r r e n t .*; // Расширение класса MyPhaser, чтобы позволить выполнять только // определенное количество фаз class M yPhaser extends Phaser { int numPhases; MyPhaser(int parties, int phaseCount) { s u p e r ( p a r t i e s ); numPhases = phaseCount - 1; } // Переопределить метод, чтобы выполнять // определенное количество фаз rot e c t e d b oolean o n A d v a n c e (int p, int regParties) { // Этот оператор p r i n t l n () нужен только для иллюстрации Обычно метод o n A d v a n c e () не отображает вывод y s t e m . o u t . p r i n t l n ("Phase " + p + " completed.\n"); // Если все фазы закончены, возвратить true 888 Часть И. Библиотека Java if(p == numPhases I I regParties == 0) return true; // В противном случае вернуть false, return false; } } class PhaserDemo2 { public static void main(String a r g s []) { MyPhaser phsr = new MyPhaser(l, 4); System.out.println("Starting\n"); new MyThread(phsr, "A"); new MyThread(phsr, "B"); new MyThread(phsr, "C"); // Ожидать завершения определенного количества фаз w h i l e (iphsr.isTerminated()) { p h s r .arriveAndAwaitAdvance(); } System.out.println("The Phaser is terminated"); } } // Поток выполнения, использующий Phaser, class MyThread implements Runnable { Phaser phsr; String name; MyThread(Phaser p, String n) { phsr = p; name = n; p h s r .register() ; new Thread(this).start() ; } public void run() { w h i l e (Iphsr.isTerminated()) { System.out.println("Thread " + name + " Beginning Phase " + p h s r .getPhase()); p h s r .arriveAndAwaitAdvance(); // Небольшая пауза для предотвращения перемешанного вывода // Только для иллюстрации. Это необязательно для правильной // работы фазера. try { Thread.sleep(10); } catch(InterruptedException e) Вывод представлен ниже В Beginning Phase 0 Thread A Beginning Phase 0 Thread С Beginning Phase 0 Глава 27. Параллельные утилиты 8 8 9 Phase 0 completed. Thread A Beginning Phase 1 Thread В Beginning Phase 1 Thread С Beginning Phase 1 Phase 1 completed. Thread СВ СВ Метод main () создает один экземпляр класса Phaser. В качестве аргумента ему передается значение 4, это значит, что он отработает четыре фазы, а затем остановится. Затем создаются три потока и начинается следующий цикл Ожидать завершения определенного количества фаз w h i l e (Iphsr.isTerminated()) { p h s r Этот цикл просто вызывает метод arriveAndAwa it Advance () , пока фазер не закончит работу, а он не закончит работу, пока не будет выполнено определенное количество фаз. В данном случае цикл продолжит выполнение до тех пор, пока не выполнятся четыре фазы. Обратите внимание на то, что потоки также вызывают метод arriveAndAwa it Advance () в пределах выполняемого цикла, пока фазер не завершит работу. Это значит, что они выполняются до завершения определенного количества фаз. Теперь внимательно рассмотрим код метода onAdvance () . Каждый раз, когда вызывается метод onAdvanceO, передается текущая фаза и количество зарегистрированных сторон. Если текущая фаза соответствует указанной или количество зарегистрированных сторон равно нулю, метод onAdvance () возвращает значение true, останавливая таким образом фазер. Это осуществляет следующая строка кода Если все фазы закончены, возвратить true |