Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

GrandM-Patterns_in_Java

.pdf
Скачиваний:
98
Добавлен:
14.03.2016
Размер:
8.88 Mб
Скачать

DoubIe Buffering 519

/ * *

*Это общая логика для предваритель ного

*заполнения резервных буферов .

*/

public synchronized void run ( ) try (

while ( ! myThread . isInterrupted ( ) " ! exhausted) (

synchronized (this) (

while ( ! outstandingFillRequest) wait () ;

)/ / whi l e

}/ / synchronized

fill () ;

outstandingFillRequest = false ;

/ / while

catch (InterruptedException е) {

/ / Ничего не делает . Это нормально .

catch (ThreadDeath е)

throw е ;

catch (Throwable е ) (

exception = е ;

finally {

exhausted = true ;

/ /

Оповещает любой поток,

//

ожидающий окончания заполнения .

synchronized (lockObject) (

lockObject . notifyAll ( ) ;

} / / synchronized

try (

in . close () ;

catch (IOException е)

if (exception==null)

exception = е ;

} / / i f / / try

in = null ;

// try

// run ( )

520 Глава 9. Шаблоны проектирования для конкурирующих операций

/* *

* Запрашивает асинхронное заполнение всех резервных буферов . * Если операция асинхронного заполнения уже приведена

* в действие , то вызов этого метода не принесет

*никакого эффе кта .

*/

synchronized void fillReserve ( ) { outstandingFillRequest = true i notify ( ) i

} / / f i l l Reserve

/ * *

*Завершает асинхронное заполнение буфера .

*/

void close ( )

myThread . interrupt ( ) i

}/ / close ( )

} / / class BufferFiller

/ / class DoubleBufferedl nputStream

Что касается класса DoubleBu fferedInputStream, то, поскольку его экземп­ ляры имеют связанные с ними потоки, они никогда не будут удалены при сборке мусорадо тех пор, пока не будут закрыты. Поток закрывается методом c lose.

ШАБЛОНЫ ПРОЕКТИРОВАНИЯ, СВЯЗАННЫЕ

С ШАБЛОНОМ DOUBLE BUFFERING

Producer-Consumer. Шаблон DоиЫе Butтering представляет собой спеuиальную

форму шаблона Producer-Consumer.

Guarded Suspension. Шаблон Guarded Suspension используется при реализаuии

шаблона DоиЫе Butтering для координации действий потоков, запрашивающих

данные, с потоком, выполняющим опережающее чтение.

Two-Phase Termination. Шаблон Two-Phase Terminationдля может быть использо­

ван при реализации шаблона DоиЫе Buffering надлежащего завершения по­

тока, выполняющего опережающее чтение.

СИНОПСИС

Объект получает запросы на выполнение каких-либодействий. Шаблон Asynchro­ nous Processing не позволяет асинхронно обрабатывать процессы. Вместо этого они ставятся в очередь и затем обрабатываются асинхронно.

КОНТЕКСТ

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

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

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

 

 

 

-+

 

 

 

 

 

1.1: ;d:-сrеаtеIDО

 

 

 

-+

1.2: generateFormLetter(;d, content)

 

 

1:

 

.------

'------

'---,

;d:..generateFormLetter(content)

 

 

 

Рис. 9.32. Синхронная версия создания письма

Первая проблема связана с производительностью. С точки зрения клиентов,

самый простой способ работы с сервером писем заключается в том, чтобы

522

Г

ава

9.

Ш

аб

л

оны проект

и

рован

и

я

дл

я конк

ури

р

ующих

опера

ц

ий

л

 

 

 

 

 

 

 

 

 

передавать серверу данные ДЛЯ стандартного письма, а затем ждать, пока сервер

не возвратит ID этого письма. Чем дольше приложение вынуждено ждать воз­

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

Приложения могут справляться с этой проблемой следующим образом: активи­

зировать поток, единственной целью которого является ожидание ID стандарт­

ного письма. Подобное решение вопроса позволяет приложениям избежать не­

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

каждого приложения, связанного с потребностью координирования дополни­

тельного потока.

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

вающих примерно в одно и то же время. Чтобы обслужить каждый запрос син­

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

Если сервер не ограничивает количество потоков, которым позволено работать параллельно, то достаточно большой пакет запросов приведет к перегрузке сер­ вера. Если количество потоков превысит лимит, производителъность быcrро упадет. Кроме того, достаточно больщое количество потоков (возможно, не­ сколько сотен) будет причиной нехватки памяти ДЛЯ сервера.

Если сервер ограничивает количество потоков, работающих параллельно, то проблема может возникнуть в том случае, если сервер уже использует макси­ мальное количество потоков. Поскольку сервер нуждается в отдельном потоке

ДЛЯ каждого обрабатываемого им запроса, ему приходится отказываться обра­

батывать запросы до тех пор, пока не завершатся некоторые текущие запросы

и потоки, обрабатывающие их, станут доступными ДЛЯ обработки других за­

просов.

Альтернативой синхронной обработке запросов может служить асинхронная

обработка (рис. 9.33).

---+

 

1.1: id :- createID()

---+

1.2.1: doGenerateFormLetter(id, content)

1.2.2: markFormCompleted(id)

 

1: id :- gепегаtеFогmLеttег(сопtепt)r----'----....J...-..,

1.2: generateForm Letter(id, content)

Рис. 9.33. Асинхронная версия создания письма

Asynchronous Processing 523

Опишем эти взаимодействия.

1.Клиент просит сервер создать стандартное письмо на основании тех данных, которые содержатся в заданном объекте. Этот вызов возвраща­ ется после того, как стандартному письму был присвоен идентиФика­ ционный номер, но, как правило, до того как создано стандартное письмо.

1.1.Сервер присваивает ID стандартному письму до его создания.

1.2.Сервер инициирует процесс асинхронного создания стандартного письма.

1.2.1.Создается стандартное письмо.

1.2.2.Стандартное письмо помещается в базу данных, где над ним могут быть выполнены дальнейшие действия.

Делая создание стандартного письма асинхронным, решают проблемы, связан­ ные с синхронной обработкой. Клиенты должны теперь Ждать всего лишь столько времени, сколько требуется дЛя присвоения ID стандартному письму. Сервер может оставить только один поток, который будет участвовать в созда­ нии стандартного письма. Если этот поток не может сразу обрабатывать запрос, он может поставить запрос в очередь, используя дЛя этой цели шаблон Producer-Consumer. Применение асинхронной обработки немного усложняет сервер, но сервер - это единственное место, которое становится более слож­ ным. Все его клиенты избавлены от подобной сложности.

Теперь рассмотрим сценарий дЛя клиента.

Предположим, проектируется GUI с использованием Swing. Ожидается, что GUI должен выглядеть так, как показано на рис. 9.34.

Рис. 9.34. Простой GUI

524

Глава 9. Шаблоны проектирования для конкурирующих операций

Если щелкнуть на пункте меню 00 It, начинает выполняться длительная опера­ ция продолжительностью несколько секунд. Если операция не будет работать асинхронно по отношению к событию GUI, которое ее запустило, и GUI,

и операция не будут себя вести надлежащим образом. Проблема возникает

из-за того, что передача событий Swing является последовательной. Если в ответ

на событие GUI команда работает синхронно, то ни одно, ни другое событие

GUI не сможет быть обработано до тех пор, пока эта команда не завершится.

Первое, что можно отметить при использовании Swing GUI в том случае, если длительная команда работает синхронно, - это отсутствие обновления экрана

до тех пор, пока не завершится команда. Это объясняется тем, что в Swing

обычно обновляется экран в ответ на событие обновления экрана. Кроме того, события обновления экрана могут генерироваться при обращении к методу refresh компонента Swing.

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

Swing позволяет команде работать асинхронно (и без использования другого потока) по отношению к тому событию, которое активизировало эту команду.

Класс j avax . swing . SwingUti l i ties имеет метод invokeLater, предназна­ ченный специальнодля этой цели. Данный метод генерирует специальное собы­ тие, которое непосредственно запускает некоторый фрагмент кода. Это событие

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

пункта меню позволяет выполняться событию обновления экрана (которое уже

находится в очереди событий) до того, как будет выполнена команда. При этом

решается проблема неисчезновения выпадающего меню.

Если команда делает что-то такое, что, как предполагается, должно изменять внешний вид пользовательского интерфейса, то ее запуск при помоши метода i nvokeLater - не самый лучший выход. Она должна работать в отдельноМ

потоке. Предположим, например, что команда показывает на экране и перио­

дически обновляет индикатор выполнения. Если эта команда вызывается при

помощи метода i nvokeLater, то индикатор выполнения не появится до тех

пор, пока не завершится эта команда. Когда индикатор выполнения наконец

появится, он будет отражать только последнее состояние, в которое его уста­ новила команда. Это объясняется тем, что экран не обновлялся до тех пор,

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

и визуально обновляемым в процессе работы команды, эта команда должна

выполняться в отдельном потоке (а не в том, который передает события).

Некоторые читатели могут заметить, что модальные диалоговые окна являются

исключениями из этого правила (которое гласит, что команда должна выпол­ няться в отдельном потоке, чтобы сделать видимыми изменения внешнего вида GUI в процессе выполнения команды). Команда может синхронно вызывать

---+
processRequest(info)

Asynchronous Processing - 52!

модальное диалоговое окно. Когда диалоговое окно прекращает свою раБО1)

команда, инициировавшая диалог, продолжает выполнение и завершается.

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

МОТИВЫ

©Предполагается, что объект должен отвечать на запросы с целью их обра ботки.

©Клиенты объекта, возможно, не должны ждать, пока он ответит на запрос. @) Запросы поступают асинхронно по отношению друг к другу. н

©Объект может получать запрос в то время, когда он все еще занят ответом

предыдущий запрос.

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

®Объект должен отвечать на запрос в течение определенного промежутк,

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

РЕШЕНИЕ

Проектируем объекты таким образом, чтобы обработка запросов происходил: асинхронно по отношению к их получению. Общая схема такого решеНИJ представлена на рис. 9.35.

1.1:

---+

1А: request(info)

Рис. 9.35. Асинхронная обработка

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

526 Глава 9. Шаблоны проектирования для конкурирующих операций

РЕАЛИЗАЦИЯ

При проектировании и реализации шаблона Asynchronous Processing следует учитывать два основных вопроса. Первый вопрос, как будут управляться запро­

сы и как будут распределены обрабатывающие их потоки. Второй вопрос воз­

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

У п р а вл е н и е з а п р о с а м и и ра с п р едел е н и е п ото ко в

Существует множество способов, позволяюших управлять запросами и распре­

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

нимается определение того, когда запрос не может быть обработан. Возможны

следуюшие варианты:

запрос немедленно может быть направлен на обработку;

обработка запроса откладывается на некоторое определенное время;

обработка откладывается до тех пор, пока не будет выполнено некоторое

условие;

запрос отклоняется и поэтому никогда не будет обработан.

Под распределением потока для обработки запроса здесьпонимается определение

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

Самый простой способ согласования запросов с потоками состоит в том, чтобы запускать новый поток каждый раз, когда должен быть обработан запрос. Ос­ новное преимущество такого подхода - его простота. Недостатком является то,

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

сами и выделением потоков. Такой подход позволяет отбрасывать запрос, но не

способен отложить его обработку. Он не обеспечивает никакой реальной поли­

тики распределения потоков или ограничения количества потоков. Возникнет

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

ния о событиях поступят примерно в одно и то же время. Если слишком боль­

шое количество событий обрабатывается одновременно, то общее количество

потоков может привести к замедлению обработки запросов или прервать ее

ввиду недостатка ресурсов.

Простая политика распределения потоков заключается в том, чтобы иметь

постоянное количество потоков, пригодных для обработки событий, и про­

извольным образом назначать события потокам, по мере того как один из них становится доступным. Это, по сути, является применением шаблона Produ­ cer-Consumer. Поток, ответственный за получение запросов и помещение их

в очередь, является производителем, а потоки, обрабатывающие запросы, явля­

ются потребителями.

Более сложная политика распределения потоков может быть реализована с по­ мощью шаблона Thread Pool (описанного в книге [Grand2001). Передача за-

Asynchronous Processing 527

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

используемых потоков. Кроме того, в зависимости от сути запроса может изме­ няться приоритет потоков.

Управление запросами может быть реализовано с помощью шаблона Scheduler. Смысл объекта-планировщика в том, что он запрещает выполнение потока до

тех пор, пока не будет выполнено некоторое условие. Применение объек­

та-планировщика позволяет отложить обработку запроса на основании практи­ чески любого критерия.

 

--+

 

 

д

i

)

I

: re ,- :...

 

_q,- uеst(

 

 

 

П О....---1 reguestProcessor

1IA.I: sсhеduLеRеquеstРrосеssiпg(iпfо)

I scheduLer

1t-

1

+--

IС:..р:..rосеssRеq:..uеst..(:..iпfо.:)....

__ __ -,

18:

--+

 

 

 

 

ехесutе(iпfо)

 

 

 

 

11

threadPooL

I

 

 

Рис. 9.36.

Обработка запроса с использованием планировщика и пула потоков

На рис. 9.36 показаны взаимодействия между объектом-планировщиком и пу­ лом потоков. При использовании простой очереди вместо пула потоков взаи­ модействия будут почти такими же.

lA. Запрос передается объекту requestProcessor.

lA.l. Объект reques tProcessor передает информацию о запросе объекту s chedu ler.

lB. Когда объект scheduler решит, что настало время обработать запрос, он передает информацию о запросе объекту threadPool.

lC. Объект threadPool выделяет поток для обработки запроса, а объект requestProcessor предоставляет необходимую логику и информацию о состоянии.

Объект schedu ler является активным, имеет свой собственный поток. Он способен распознавать, когда запрос готов к обработке, и передает его пулу по­ токов независимо оттехдействий, которые выполняют любые другие потоки.

Если нужно сохранить потоки, можно реализовать объект scheduler как пас­ сивный объект, который не имеет собственного потока. Тогда каждый раз при передаче запроса объекту schedu ler этот объект будет проверять, имеются ли у него какие-либо запросы, готовые к передаче в пул потоков. Когда один из потоков пулазавершает обработку некоторого запроса, он может (но не обязан) вызвать метод объекта scheduler, который проверит наличие запросов, гото­ вых к обработке.

528 Глава 9. Шаблоны проектирования дпя конкурирующих операций

У п р а вл е н и е р е зул ьтатом

В некоторых случаях после окончания обработки запроса другой объект может

обнаружить, что обработка запроса завершена. Чаще всего объект, КОТОРыйЭТО,

как полагают, должен быть осведомлен об окончании обработки запроса, -

тот объект, который выдал запрос. Существуют два самых распространенных

способа, позволяющих другим объектам узнать о том, что обработка некоторо­

го запроса завершена.

1 . Объект, отвечающий за окончание обработки запроса, может отправлять

событие тем объектам, которые были зарегистрированы как получатели таких событий.

2.Результат обработки события может быть сохранен в таком месте, в которое заинтересованные объекты могут отправлять запросы с целью обнаружения запросов и их результатов.

Если считается, что объект, который выдал запрос, узнал о результате обработ­

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

СЛЕДСТВИЯ

©Объект, являющийся источником запроса, не должен ждать окончания об­ работки этого запроса, поэтому он может выполнять другие действия, на­ пример, отправлять следующие события.

©Запрос может быть поставлен в очередь при отсутствии выделенного для него потока. Это большая экономия ресурсов, поскольку каждый поток ис­ пользует значительный объем памяти, даже если он ничего не делает.

©Шаблон Asynchronous Processing позволяет проводить явную политику опре­

деления того, когда запрос будет обработан или будетли он обработан вообше.

®Шаблон Asynchronous Processing не позволяет с уверенностью гарантиро­

вать, что запрос будет обработан в течение некоторого определенного вре­

мени.

ПРИМЕНЕНИЕ В JAVA API

Полыовательские интерфейсы, основанные на Swing и Аwr, используют шаб­

лон Asynchronous Processing. Они управляют событиями клавиатуры и мыши,

применяя шаблон Producer-Consumer, и осуществляют их последовательное

управление, используя единственный поток.

Процесс работает следуюшим образом. Когда пользователь делает что-нибудь

с клавиатурой или мышью, платформно-зависимый механизм создает соответ­ ствующий объект, который описывает это событие. Затем он помещает событие

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]