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

GrandM-Patterns_in_Java

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

Lock Object 459

Обратите внимание, что класс Abs t ractGameObject имеет метод getLockOb­ j ect. Этот метод возвращает объект, который будет использоваться как объект

блокировки. Кроме того, этот класс имеет свойство типа boolean, позволяю­ шее определять, выделяется ли объект. Следующий листинг представляет собой

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

class GameCharacter extends AbstractGameObject {

private ArrayList myWeapons = new ArrayList ( ) i

public void dropAllWeapons ( ) {

 

synchronized

(getLockObject ( »

{

for

(int i

= mуWеаропs . sizе () -l i i>=O i i- ) {

«

Weapon) myWeapons . get (i»

. setGlowing (true) ;

}// for

}// synchroni zed

// dropAl lWeapons ( )

// class GameCharacter

Класс GameCharacter имеет метод dropAllWeapons , который начинает бло­

кировку объекта блокировки, возвращенного методом getLockObj ect. Он на­ свледует этот метод от класса Abs tractGameObj ect. Затем он устанавливает true свойство свечения объекта оружия, связанного с этим героем. Обладая

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

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

С ШАБЛОНОМ LOCK OBJECT

Single Тhreaded Execution. Шаблон Lock Object представляет собой более изящ­

ный вариант шаблона Single Threaded Execution.

Этот шаблон основан на материале, который был представлен в работе [Lea97].

СИНОПСИС

Если существует условие, которое запрещает методу выполнять его функции,

то данный шаблон приостанавливает выполнение этого метода до того момен­

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

КОНТЕКСТ

Предположим, нужно создать класс, который реализует структуру очереди (queue) данных. Это структура данных вида <mервым зашел - первым вышем,

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

лись в очередь. На рис. 9.9 представлен класс Queue.

Queue isEmptyO:boolean

push(:Object) {concurrency"guarded} pull():Object {concurrency-guarded}

Рис. 9.9. Класс Queue

Класс Queue имеет два интересных для нас метода:

push добавляет объекты в очередь;

pul l удаляет объекты из очереди.

Если метод ри11 объекта Queue вызывается в тот момент, когда очередь пуста,

он не возвратит объект из очереди до тех пор, пока в очереди не появится объ­

ект, который должен будет возвращаться методом ри11. Объект может быть до

бавлен в очередь в то время, пока метод ри11 находится в состоянии ожиданиЯ,

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

позволяет многочисленным потокам безопасно выполнять параллельные обра­

щения к ним.

Если просто сделать оба метода синхронизированными, может возникнуть сле­

дующая проблема: в момент вызова метода ри11 объекта Queue очередь пуста.

Метод ри11 будет ожидать, пока в результате вызова метода push не появитСЯ

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

Guorded Suspension 461

закончит работу метод pull, а метод pu l l не закончит выполнение до тех пор, пока не выполнится метод push.

Решением этой проблемы является задание предварительного условия для ме­ тода pul l , чтобы он не выполнялся, если очередь пуста.

На рис. 9. 10 показаны параллельные обращения к методам push и pul l объек­

та Queue. Если метод pull вызывается в тот момент, когда метод isEmpty объ­

екта Queue возвращает true, то поток ожидает до тех пор, пока метод isEmpty не возвратит false. Только тогда сможет выполниться метод pul l . На самом деле невозможно выполнить метод pu ll в то время, пока очередь пуста, поэто­ му не существует проблемы с вызовом метода push, который может добавлять объекты в пустую очередь.

lla: puLl() {сопсurrепсу-guаrdеdl!qisЕmрty()}

 

lb: pushO {concurrency-guarded}1

 

 

 

 

 

Рис. 9.10. Взаимодействие ДIlЯ очереди

МОТИВЫ

©Чтобы иметь возможность выполнять безопасные параллельные обращения

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

©Объект может находиться в таком состоянии , когда один из его синхрони­

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

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

РЕШЕНИЕ

Рассмотрим рис. 9. 1 1 , на котором показан класс элемента управления окном

Widget. Этот класс имеет два синхронизированных метода: [оо и bar. Объекты

Widget могут иметь особое состояние. Когда объект Widget находится в таком

состоянии, его метод i sOK возвращает false; в противном случае он возвращает true. Если объект Widget находится в особом состоянии, обращение к его мето­

ду bar может вывести его из этого состояния. Помимо вызова метода bar, не

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

существует другого способа вывести объект Widget из особого состояния. Вы­

ведение объекта Widget из особого состояния - это побочный эффект выпол­

нения основной функции метода bar, поэтому нельзя вызывать метод bar

только для того, чтобы вывести из особого состояния объект Widget.

Widget

IsOKO:boolean

fooO {concurrency-guarded} bar(:int) {concurrency-guarded}

Рис. 9.11. Класс Widget

Обращение к методу foo объекта Widget не может завершитьсяТО, , если объект

Widget находится в особом состоянии. Если это случилось, поскольку мето­

ды foo и bar являются синхронизированными, последующие обращения к ме­ тодам foo и bar объекта Widget не будут выполнены до тех пор, пока не закон­

чится выполнение предьщущего вызова метода foo. Вызов метода foo не закончит выполнение до тех пор, пока вызов метода bar не выведет объект Widget из особого состояния.

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

Guarded Suspension приостанавливает поток до тех пор, пока состояние объек­

та не позволит методу завершиться (рис. 9. 1 2).

11a: fooO {concurrency.guarded I !w.isOK(}}

 

 

lЬ: bar(:int) {concurrency-guarded}1

 

 

 

 

 

 

 

w:Widget

Рис. 9.12. Взаимодействие ДЛЯ wаблона Guarded Suspension

Заметим, что на рис. 9 . 1 2 указано предварительное условие, которое должно быть удовлетворено перед тем, как начнет выполняться обращение к метоДУ foo объекта Widget. Если поток пытается вызвать метод foo объекта Widget

в тот момент, когда метод isOK объекта Widget возвращает faJse, поток должен

будет ожидать до тех пор, пока метод i sOK не возвратит true; тогда сможет BbI­

полниться метод foo. Пока такой поток ждет возврата значения true от метода i sOK, другие потоки могут беспрепятственно вызывать метод bar.

 

Guarded Suspension 8

463

РЕАЛИЗАЦИЯ

 

 

Шаблон Guarded Suspension

реализуется с использованием методов

wai t

и noti fy следуюшим образом:

 

class Widget (

 

 

synchronized void foo ( ) {

 

while ( ! isOK ( »

(

 

} wait ( ) ;

 

 

synchronized void bar (x lnt) (

notify ( ) ;

}

Этот механизм действует так: метод, например foo, перед тем как начать реаль­ но выполняться, должен проверить выполнение некоторого предусловия. Пре­ жде всего этот метод должен проверить это предусловие в цикле while . Если предусловие ложно, то вызывается метод wai t.

Каждый класс наследует метод wai t от класса Obj ect. Когда поток вызывает метод wai t объекта, этот метод заставляет поток заблокировать объект. Затем метод ждет, пока ему не будет сообшено о разрешении разблокировать объект. далее, как только поток сможет снова осушествлять блокировку, метод wai t заканчивает свою работу.

После возврашения метода wait управление снова передается в самое начало цикла whi le, где повторяется проверка предварительного условия. Проверка предварительных условий в цикле обусловлена тем, что в промежутке между тем моментом времени, когда поток пытается в первый раз осушествить син­

хронизационную блокировку, и тем моментом времени, когда ему это удается,

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

Вызов метода wai t оповещается о необходимости выполнения в том случае,

когда другой метод, например, bar, вызывает метод not i fy объекта. Методы

вызывают метод noti fy после того, как они изменили состояние объекта та­

КИм образом, чтобы могли удовлетворяться предварительные условия метода.

Метод not i fy представляет собой другой метод, наследуемый всеми классами

от класса Obj ect. Задача метода not i fy в том, чтобы оповестить другой поток, ожидающий окончания выполнения метода wa i t, О необходимости окончания

метода wai t .

Если в состоянии ожидания находится несколько потоков, метод not i fy выби­ рает один из них случайным образом. Произвольный выбор хорошо работает

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

вбольшинстве ситуаций. Но он не сможет работать нормально для таких объ­

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

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

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

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

сушествует вероятность того, что метод никогда не закончит завершение, так

как этот метод никогда не будет извещен об удовлетворении своих предвари­

тельных условий.

Для тех классов, которые не приемлют произвольного выбора при принятии

решения, существует альтернативный способ решения вопроса, какой поток известить. Методы таких классов могуг вызвать метод noti fyAl l . Вместо опо­

вещения одного выборочного потока метод noti fyAll оповещает все ожидаю­

шие потоки. При этом решается проблема, связанная с отсугствием оповеще­

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

СЛЕДСТВИЯ

©Шаблон Guarded Suspension позволяет потоку решать проблемы объекта, состояние которого не позволяет выполнить некоторую операцию, посред­

ством приостановки до того момента, пока объект не будет в состоянии вы­

полнить эту операцию.

Шаблон Guarded Suspension использует синхронизированные методы или синхронизированныеждуг команды. Возможна такая ситуация, когда несколько потоков выполнения вызова одного и того же метода. Шаблон Guarded

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

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

объекта позволит выполнить метод. Для этой цели можно использовать

шаблон Scheduler.

®Если существует вложенная синхронизация, использование шаблона Guarded

Suspension может быть весьма затруднительным. Рассмотрим диаграмму

взаимодействия (рис. 9. 1 3).

Доступ к объекту Widget осуществляется при помощи lсинхронизирова ан­

ных методов объекта Thing. Это значит, что если поток вызывает метод foo объекта Widget именно в тот момент, когда состояние объекта Widget

заставляет его метод i sOK возвращать false, то поток будет бесконечНО

ждать, пока метод i sOK объекта Widget не возвратит true. Причина в том, что методы объекта Thing синхронизированы без каких-либо предвари­ тельных условий. Возникшая проблема является именно той задачей, ДJlЯ решения которой и предназначен шаблон Guarded Suspension.

 

 

 

 

 

 

Guarded Suspension - 465

11a: doThisO {concurrency-guarded}

 

 

 

lЬ: doThat(:int) {concuгrency-guarded}1

 

 

 

llа.l: fooO {concurrency-guarded I !w.isOKO}

I

 

 

 

I

 

 

 

 

I

 

 

 

lЬ.l: bar(:int) {concurrency-guarded}1

 

 

w:Widget

 

I

 

 

 

 

Рис. 9.13. Guarded Suspension в условиях вложенной синхронизации

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

Класс j ava . ne t . InetAddress использует шаблон Guarded Suspension с це­ лью управления внyrренней оптимизацией. Одной из обязанностей класса Ine tAddres s является нахождение в сети адреса, который соответствует име­ ни хоста. Поскольку для определения адреса, соответствующего некоторому имени в сети, нужно обращаться к одному или нескольким удаленным серве­

рам, эта операция является достаточно длительной.

Как только класс InetAddres s определил сетевой адрес, соответствующий имени хоста, он помещает этот адрес в кэш. Кэш является общим для всех объ­

ектов InetAddress . Когда объект InetAddress должен находить в сети адрес,

соответствующий имени хаста, прежде всего он ищет его в кэше. Если иско­ мый адрес содержится в кэше, объекту InetAddress не нужно обращение к ка­ кому-либо удаленному серверу.

В то время когда объект I netAddress получает адрес, соответствующий неко­ торому имени хоста, другому объекту InetAddress может понадобиться адрес, соответствующий тому же имени хоста. Пока первый объект добывает этот адрес, нет никакого смысла в том, чтобы второй объект искал тот же адрес в кэше. Адрес не появится в кэше до тех пор, пока первый объект InetAddres s не- по­ Лучит его. Самое лучшее, что может сделать второй объект Ine tAddress , это

ждать, пока первый объект InetAddress не найдет этот адрес. Именно в такой ситуации класс InetAddress использует шаблон Guarded Suspension.

Передтем как проверить, не содержит ли кэш адрес хоста, объект InetAddress

должен узнать, не занят ли другой объект InetAddress получением этого же

адреса. Если другой объект InetAddres s получает искомый адрес, то первый

объект InetAddress ожидает до тех пор, пока второй объект InetAddress не получит этот адрес и не положит его в кэш. Только тогда первый объект

InetAddress продолжит свои действия и обратится в кэш с целью получения

нужного адреса хоста.

466 Глава 9. Шаблоны проеКТИРОВQНИЯ для конкурирующих операций

ПРИМЕР КОДА

Приведем КОд. реализуюший проект класса Queue, рассмотренный в разделе

«Контекст» :

public class Queue {

private ArrayList data - new ArrayList ( ) ;

/* *

* Помещаем объект в конец очереди .

* /

synchronized public void put (Object obj )

{

 

data . add (obj ) ;

 

 

notify ( ) ;

 

 

/ / put ( Obj ect)

 

/ * *

 

*

Получаем объект, находящийся в начале

очереди .

*

Если очередь пуста , ждем, пока в ней

не появится объект .

* /

 

synchronized public Object get ( )

 

 

while (data . size ( ) == О) {

 

 

try {

 

wait ( ) ;

catch (InterruptedException е) {

// try

// while

Object obj = data . get (O) ; data . remove (O) ;

return obj ;

// get ( )

// class Queue

Обратите внимание, что в последнем листинге вызов метода wai t находится

внутри оператора try, который генерирует исключительную ситуацию Inter­ ruptedExcept i on. При вызове метода wai t может быть сгенерирована эта ис­

ключительная ситуация. Самое простое поведение программы - проигнориро­

вать InterruptedExcept ion, которая может генерироваться методом wait.

В описании шаблона Two-Phase Termination содержатся пояснения, когда ме­

тод wai t генерирует I nterruptedException и что нужно делать в этом случае.

Guorded Suspension - 467

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

кBalking. Шаблон Balking предоставляетдругую стратегию обработки обращений

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

Тwo-Phase Termination. Реализация шаблона Two-Phase Termination обычно предполагает генерацию и обработку исключительной ситуации I л tеrruрtеd­ сЕхсерt iол. В его реализации, как правило, присутствует взаимодействие

шаблоном Guarded Suspension.

Этот шаблон основан на материале, представленном в работе [Lea97].

СИНОПСИС

Если метод объекта вызывается в тот момент, когда состояние объекта не по­

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

КОНТЕКСТ

Предположим, создается программа, управляюшая электронным устройством06- смывания для туалета. Эти устройства предназначены для использования в шественных уборных. Смывное устройство имеет датчик освешенности, уста­ новленный на его передней панели. Когда он фиксирует повышение уровня освешенности, он полагает, что человек вышел из туалета, и приводит в дви­ жение струю воды. Электронное смывное устройство для туалета имеет также кнопку, которая может использоваться для ручного включения водяной струи. На рис. 9. 14 изображена диаграмма классов, моделируюших такое поведение.

 

I LightSensor I

 

 

 

 

I FlushButton I

Requests-flush-

 

 

 

Rusher

 

 

Reques

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

flush()

 

 

 

 

 

 

Рис. 9.14.

Классы смывного устройства

 

 

Если объект LightSensor или объект FlushButton решает, что нужна струя

воды, для ее запуска он запрашивает объект Flusher, вызывая его метод flush. Метод flush запускает струю воды и, как только появляется водяной

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

некоторых вопросов конкурентности.

Нужно решить, что произойдет в том случае, если в момент вызова метода fl ush водяная струя уже приведена в действие. Придется решить также, что бу­

дет, когда оба объекта, LightSensor и FlushButton, вызывают метод flush

объекта Flusher одновременно.

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

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