Try English version of Quizful



Раздаем бесплатные Q! подробности в группе Quizful.Alpha-test
Топ контрибуторов
loading
loading
Знаете ли Вы, что

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

Лента обновлений
ссылка 12:31:40
Комментарий от log4456602:
Я так понял для варианта :
String a1 = "Test";
String...
ссылка 12:05:43
Добавлен вопрос в тест Базы данных (теория)
ссылка 11:22:41
Комментарий от log4456602:
Действительно!)))
ссылка 00:08:51
Добавлен вопрос в тест XML
ссылка 00:06:23
Добавлен вопрос в тест XML
Статистика

Тестов: 153, вопросов: 8597. Пройдено: 417952 / 2039319.

Параллельные утилиты

head tail Статья
категория
Java
дата29.09.2014
авторteinnsei
голосов9

Предисловие

Перед изучение данной темы, настоятельно рекомендую ознакомится с многопоточным программированием или для более детального ознакомления Г.Шилдт Java “Полное руководство”.

Вступление

Язык Java с самого начало своего развития имела встроенную поддержку для создания многопоточных программ. Для создания потоков можно использовать интерфейс Runnable, а также класс Thread. Для синхронизации методов используется ключевое слово synchronized. Достижение многопотоковых коммуникаций получается за счет монитора (используя методы wait и notify определенные в классе Object). Тем не менее порой этих критериев недостаточно при создание сложных программ где задействовано много потоков. Чем больше потоков используется в программе, тем сложнее кодировать и понимать такую программу.

Начиная с JDK 5 были добавлены параллельные утилиты для упрощения создания многопоточных программ. Основные утилиты: Semaphore (семафор), CountDownLatch (защелка с обратным отсчетом), CyclicBarrier (циклически барьер), Exchanger (обменик), а также с JDK 7 был добавлен Phaser (фазер).

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

Все эти классы находятся в пакете java.util.concurren.

Semaphore (семафор)

Основная задача семафора — управление доступом к ресурсу с помощью счетчика. Для получения доступа к объекту, поток запрашивает у семафора разрешение. Если счетчик больше нуля, то доступ разрешен, в противном случае потоку нужно будет ждать пока счетчик не станет больше нуля. Такая тенденция дает альтернативный способ создания межпотоковой коммуникации.

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

В даном коде создается объект класса Data у которого есть два синхронизированных метода. Один метод send, а другой get. Эти методы создают межпотоковую коммуникацию за счет использования notify и wait. Пока один поток вошел в монитор этого объекта, второй будет ждать до тех пор, пока ему не предоставят доступ (вызов notify), после чего первый поток покинет монитор и предоставит доступ другому потоку.

Задача метода get - вывести значение на терминал (то есть получить значение). Задача метода send — присвоить значение полю count полученое через параметр.

Так же в программе создаются два потока. Поток класса Senable и Getable. Обоим потокам передается один и тот же объект класса Data. Задача потока Senable формировать новые значение и присваивать их объекту. Задача потока Getable получать данные (просто вызов метода отвечающий за вывод полученных данных).

Попробуем решить туже задачу используя семафор. Итак класс семафор имеет два контструктора

  • Semaphore(int количиство)
  • Semaphore(int количиство, boolean как)

Первый конструктор указывает какому количеству потоков разрешается доступ. Второй конструктор указывает количество потоков, а также в каком порядке разрешать вход в монитор. Если значение будет true, тогда доступ будет предостовлятся в том порядке, в котором потоки подают запрос, если же значение false, то в произвольном порядке. Так же у семафора определено два важных метода.


void acquire () throws InterruptedException
void acquire (int количиство) throws InterruptedException

Первая форма метода запрашивает единичный доступ к ресурсу. Вторая форма указывает количество разрешений. Если доступ не разрешен, поток приостанавливается до тех пор, пока семафор не предоставит доступ.

Второй метод напротив освобождает ресурс.


void release()
void release(int количиство)

Зная данные методы, попробуем решить туже задачу используя семафор .

С начало создается два объекта семафор. Один разрешает доступ одному потоку, другой не одному. Это нужно для того, чтобы первый работу начал поток типа Sendable. После чего начинается чередование семафоров.

Даний пример не показывает всех возможностей семафора. Например: одно из главных преимущест семафора то, что он позволяет владеть объектом одновременно нескольким потоком, а не только одному. Это делается за счет инициализации счетчика в конструкторе.

CountDownLatch (защелка с обратным отсчетом)

Данная утилита предоставляет возможность одному потоку ожидать выполнение до тех пор, пока не выполнится одно или несколько событий. Рассмотрим пример. Пускай один поток отвечает за оповещение пользователя об окончании определенных действий, а второй поток выполняет их.

С начало в программе создается объект «защелка» который передает в конструктор значение пять. Данное число указывает, что должно произойти 5 событий. Затем создается два потока класса Echoable и Massager. Первый поток выводит значение локальной переменной, после чего выполняется уменьшение счетчика событий на 1 (вызов метода countDown класса CountDownLatch). Тем временем в классе Massager вызывается метод await который заставляет приостановить поток, до того момента, пока счетчик событий не станет равен нулю. После чего он может продолжить свою работу. Поэтому после того как выполняется 5 итераций метода countDown защелка снимется и даст возможность потоку Massager продолжить свою работу.

CountDownLatch имеет один конструктор, которому необходимо передать количество событий. Одно выполненное событие равно одному вызову метода counDown() - данный метод ничего не возвращает, и ничего не принимает. Чтобы поток заставить ждать окончания всех событий, необходимо вызвать метод await. Данный метод имеет две формы.


void await() throws InterruptedException
boolean await(long сколько, TimeUnit tu) throws InterruptedException

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

CyclicBarrier (циклический барьер)

Данная утилита похожа на «защелку». Тем не менее они имеют различия. Циклический барьер позволяет определить объект синхронизации. Каждый поток выполняется параллельно, и как только каждый поток достигает барьерную точку, считается что цикл окончен.

Для начало нужно запомнить, что класс CyclicBarrier предоставляет 2 конструктора.

  • CyclicBarrier(int количество)
  • CyclicBarrier(int количество, Runnable действие)

Первая форма конструктора позволяет задать количество потоков которые должны дойти до барьерной точки. Вторая форма позволяет задать также действие, которое должно выполнится после достижения барьера всеми потоками. Второму параметру должен передаваться класс реализующий интерфейс Runnable. Поток НЕ НУЖНО ЗАПУСКАТЬ самостоятельно, данная утилита это делает автоматически после достижения барьера.

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


void await() throws InterruptedException
boolean await(long сколько, TimeUnit tu) throws InterruptedException, BrokenBarrierException, TimoutException.
Для того, чтобы понять лучше работу данной утилиты рассмотрим пример.

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

Exchanger (обменик)

У программиста может возникнуть ситуация когда два потока тесно связаны между собой. Предположим один поток формирует данные, а другой записует эти данные в файл. Поэтому первый поток передает данные второму потоку. Пока поток 1 формирует данные, поток 2 занимается созданием файла, проверкой и т.п. Для того, чтобы передать данные с одного потока в другой поток в определенной точки работы обоих потоков, используется специализированная утилита «обменик».

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


V exchange(V объект) throws InterruptedException
V exchange(V обхект, long время, TimeUnit tu) throws InterruptedException, TimeoutException.

Первому параметру должна передаваться ссылка на объект, который будет передан другому потоку.

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

Phaser (фазер)

Самым интересным инструментом является фазер. Он похож на циклический барьер, но выполняет другую ф-цию. Предположим у нас есть несколько потоков, которые запущенны параллельно из главного. Каждый из потоков с начало создает переменную, затем присваивает значение, после чего выводит ее значение на терминал. Условно разделим роботу потока на 3 фазы: создание, присвоение, вывод. Мы хотим, чтобы потоки работали синхронно по фазам. То бишь, потоки не продолжают работу до тех пор, пока каждый из потоков не завершит фазу.

Прежде чем приступим к рассмотрению примера рассмотрим сам класс.

Он имеет большое количество методов, а также перегруженные конструкторы.

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

    Phaser() Phaser(int party)

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


int register()

Данный метод вызывается из потока который хочет зарегистрироваться. В результате возвращается номер зарегистрированной фазы.


int arrive()

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


int arriveAndAwaitAdvance()
Этот метод указывает что он завершил выполнение фазы и приостанавливает текущий поток до момента, пока все потоки не закончат выполнять данную фазу. Также он возвращает номер фазы.

int arriveAndDeregister()

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

Чтобы детально понять работу фазера, рассмотрим пример:

Изначально фазеру указывается количество сторон — равен одному. Это нужно затем, что главный поток не регистрируется. Дальше создаются три потока MyThread которым передается строка и фазер. В конструкторе каждого потока регистрируется сторона. Затем запускается выполнения каждого потока. За счет методов arriveAndAwaitAdvancе каждый поток не продолжает свою работу до тех пор пока все не закончат текущею фазу. В конце вызывается метод arriveAndDeregister который отменяет регистрацию сторон. В главном потоке (main) так же вызываются эти методы, который дожидается окончание работы фаз, после чего выводит номер завершения фазы. В конце главный поток отменяет свою регистрацию и программа завершается.

TimeUnit

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

  • DAYS
  • HOURS
  • MINUTES
  • SECONDS
  • MICROSECONDS
  • MILLISECONDS
  • NANOSECONDS

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

В завершении

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

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

Послесловие

Параллельные утилиты это не конец возможностям Java в вопросе многопоточия. Существуют так же параллельные коллекции которые работают в синхроном режиме. Они находятся в пакете java.util.concurrent. Так же с JDK 7 разработчики добавили параллельное программирование. Потоки не являются полностю параллельными, они используют параллелизм на основе квантование времени. Если вы хотите программировать параллельно на основание многоядерных процессоров рекомендую изучить структуру Fork\Join Framework (JDK7).

Если Вам понравилась статья, проголосуйте за нее

Голосов: 9  loading...
Heorhi_Puhachou   Selfing   xDezmond   SamTan   dyack822   MAXIMUM13   freedomserg   Gans2008   Maksim87