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

GrandM-Patterns_in_Java

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

438 Глава 8. Поведенческие шаблоны проектирования

docElement = myDocument . getChild (docIndex)

;

docIndex += 1 ;

if (docElement instanceof Paragraph) return (Paragraph) docElement;

return null;

1 1 getNextParagraph ( )

} 1 1 class DocumentVisitor

Следующий листинг - это класс ReorgVis i tor, который должен «посещать»

абзацы документа и перемещать в отдельный документ те из них, которые от­

носятся к заданному уровню структуры оглавления.

class ReorgVisitor extends DocumentVisitor private TocLevel [ ] levels ;

ReorgVisitor (Document document, int level) super (document) ;

this . levels = document . getTocLevels ( ) ; Paragraph р;

while « р = getNextParagraph (» ! = null)

}11 while

1 1 constructor ( Document)

1 1 class ReorgVisitor

впоследнем листинге описан класс TOCVis i tor. Класс TOCVisi tor отвечает

за создание оглавления. Он более глубоко проникает в CтpYJ<1YPy объектов доку­ мента, манипулируя при этом объектами Document, Paragraph и LineOfText.

Он использует объект Li neOfText, потому что элемент оглавления будет со­

держать текст из первого объекта LineOfText из абзаца, которому соответству­

ет элемент оглавления.

class TOCVisitor extends DocumentVisitor ( private Hashtable tocStyles = new Hashtable () ;

TOCVisitor (Document document) super (document) ;

TocLevel [ ] levels = document . getTocLevels ( ) ;

1 1 Помещаем стили в хэш-таблицу .

for (int i=O ; i < levels . length ; i++)

tocStyles . put (levels [i] . getStyle () , levels [i] ) ;

}1 / for

11 constructor (Document )

Visitor 439

ТОС buildTOC ()

{

 

ТОС toc = new ТОС ( ) ;

 

Paragraph р ;

 

 

while

« р = getNextParagraph (» ! = null)

String styleN&me = p . getStyle () ;

if

(styleName ! = null)

{

 

 

TocLevel level ;

 

 

 

level =

(TocLevel) tocstyles . get (styleName) ;

 

 

if (level ! = null)

{

 

 

LineOfText firstLine = null ;

 

 

for

(int i = О ; i < p . getChildCount () ; i++) {

 

 

DocumentElementIF е = p . getChild (i) ;

 

 

if

(е instanceof

LineOfText) {

 

 

 

firstLine = (LineOfText) е ;

 

 

 

break;

 

 

 

 

//

i f

 

 

 

)

//

for

 

}

 

//

if

 

 

// if

 

 

 

 

 

 

 

/ / while

return toc;

// buildTOC ( )

//class TOCVisitor

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

вIterator. Шаблон Iterator представляет собой альтернативу шаблону Visitor

том случае, когда структура объектов, по которой осушествляется навигация,

ЯВЛЯется линейной.

Little Language. Шаблон Visitor можно использовать в шаблоне Little Language дЛя реализации некоторой его части.

Composite. Шаблон Visitor часто используется вместе с такими структурами

объектов, которые организованы при помоши шаблона Composite.

'Л А В А

Шаблоныпроектирования

v

ДЛ Я конкурирующих операции

ngle Threaded Execution (Однопоточное выполнение) (443) )ck Object (Объект блокировки) (453)

uarded Suspension (Охраняемая приостановка) (460) !lking (Отмена) (468)

:heduler (ПЛанировщик) (473)

ad/Write Lock (Блокировка чтения/записи) (483) roducer-Consumer (Производитель-потребитель) (493) wo-Phase Tennination (Двухфазное завершение) (499) ouble Buffering (Двойная буферизация) (504) iупсhrопоus Processing (Асинхронная обработка) (521) Jture (Будущее) (534)

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

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

IYX разных типов,

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

ное ограничение операций может привести к их взаимной блокировке и не­ возможности выполнения.

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

2.

Взаимная блокировка (deadlock) - это ситуация, когда одна операция, преж­

де чем продолжить выполнение, ОЖИдает действие второй операции. Если

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

смертельным объятием (deadly еmЬгасе).

Последовательность операций. Если операции вынуждены получать доступ

к общему ресурсу одновременно, может возникнуть необходимость в том,

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

Шаблон Single Threaded Ехесutil!П - это самый важный шаблон данной главы.

Большая часть вопросов, относящихся к совместно используемым ресурсам,

может быть решена только при помощи шаблона SingIe Threaded Execution, ко­

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

ются менее распространенными, и они могут обрабатываться с помощью

шаблона Scheduler.

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

Если некоторая операция нуждается в монопольном доступе к нескольким ре­ сурсам, шаблон Lock Object позволяет упростить координацию доступа к не­

скольким ресурсам.

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

либо немедленно, либо никогда.

Шаблон ReadjWrite Lock обеспечивает альтернативный вариант доступа вида

«один поток В какой-то момент», если одни операции могут совместно исполь­

зовать один и тот же ресурс, а другие операции этого делать не могут.

Шаблон Producer-Consumer координирует объекты, создающие некоторый ре­

сурс, и объекты, использующие этот ресурс. для

Шаблон Two-Phase Termination применяется правильноro последователь­

ного закрытия потоков.

Шаблон ОоиЫе Buffering представляет собой специальную версию шаблона

Producer-Consumer. Он позволяет создавать необходимые ресурсы заранее, т.е.

до того как они понадобятся.

Шаблон Asynchronous Processing позволяет избежать ожидания результатоВ

операции, если этот результат не нужен немедленно.

Шаблон Future позволяет классам, вызывающим операцию, не знать о тоМ, синхронной или асинхронной является эта операция.

Шаблон Single Threaded Execution известен также под названием Critical Section.

СИНОПСИС

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

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

КОНТЕКСТ

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

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

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

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

представляющие количество машин, прошедших за одну минуту. На рис. 9. 1

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

Рассмотрим эти классы.

TrafficSensor. Каждый экземпляр этого класса соответствует физическому сенсор­

Ному устройству. Каждый раз, когда машина проходит мимо физического сен­

Сорного устройства, соответствующий экземпляр класса TrafficSensor вызы­

вает метод vehiclePassed класса TrafficSensorController. Каждый объ­

ект TrafficSensor выполняется в своем собственном потоке, что позволяет

Управлять входными данными от связанного с ним сенсора асинхронно по от­ Ношению к другим сенсорам.

TrafficTransmitter. Экземпляры этого класса отвечают за передачу данных о ко­

Личестве машин, прошедших через некоторое место на дороге в течение одной

Минуты. Объект Tra f f i cTransmi tter получает величину, обозначающую

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

количество машин, прошедших определенное место дороги, вызывая метод getAndClearCount соответствуюшего объекта Tra fficSensorController. Метод getAndClearCount объекта TrafficSensorController возвращает

число, обозначающее количество машин, прошедших мимо сенсора с момеНта предьщущего вызова метода getAndClearCount.

 

 

 

 

 

 

TrafficSensor

1..*

 

 

 

 

1 ,

Оповещает о прохождении транспортного средства......

 

TrafficSensorController

 

 

vehicleCount:int

 

 

vehicLePassed()

 

 

 

 

getAn dCLearCount():int

 

 

1

 

 

 

 

 

 

 

 

 

1

Получает данные по учету движения транспорта ....

 

 

 

TrafficTransmitter

 

Рис. 9.1. Классы датчика движения транспорта

TrafficSensorController. Экземпляры класса TrafficSensor и класса TrafДЛЯ­ ficTransmitter вызывают методы класса TrafficSensorController

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

Возможна ситуация, когда два объекта TrafficSensor вызывают метод vehi­ clePassed объекта TrafficSensorControl ler одновременно. Если оба вы­

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

Предполагается, что каждое обращение к методу vehiclePassed должно увели­

чивать значение счетчика машин на единицу. Однако, если в один и тот же мо­

мент времени выполняются два обращения к методу vehicle Pa ssed, счетчик

машин увеличивается не на два, а на единицу. Опишем последовательность со­

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

1. Оба вызова одновременно считывают одно и то же значение vehicleCount.

2. Оба вызова добавляют единицу к одной и той же величине.

З. Оба вызова сохраняют одно и то же значение в vehicleCount.

Очевидно, что, допуская одновременное выполнение нескольких обращениЙ

к методу vehiclePa ssed, в результате получим число, которое меньше реаль­

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

машин не может представлять серьезной угрозы для этого приложения, суше-

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

Объект TrafficTransmitter периодически вызывает метод getAndClearCount

объекта Traff icSensorController. Метод getAndC learCount считывает зна-

Single Threaded Execution 445

чение переменной vehi cleCount объекта TrafficSensorContro l ler, а затем сбрасывает ее в ноль. Если методы vehiclePassed и getAndC learCount объекта TrafficSensorControl ler вызываются одновременно, создается ситуация, которая называется состязание.

Состязание (гасе condition) - это ситуация, исход которой зависит от порядка завершения конкурирующих операций. Если последним завершается метод

вgetAndClearCount, то он устанавливает значение переменной vehicleCount

ноль, удаляя результат вызова метода vehiclePassed. Это просто другой

способ неправильного подсчета машин, когда значение счетчика меньше ре­ ального количества машин. Однако проблема становится более серьезной, если последним финиширует метод vehiclePassed.

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

Большое значение счетчика может привести к тому, что центральный компью­ тер сделает вывод, что началась дорожная <mробка» и что он должен показать на электронных знаках сообщения, предлагающие водителям следовать по дру­ гим маршрутам. Тогда ошибка подобного рода действительно может стать при­ чиной возникновения дорожной «пробкИ» .

Если потребовать, чтобы в какой-то момент только один поток выполнял метод

vehiclePassed или метод getAndClearCount объекта TrafficSensorCon­ troller, это будет простым решением проблемы. Можно указать такое про­ ектное решение, обозначив параллельность этих методов как охраняемую (guarded). В схемах UML обозначение охраняемой параллельности метода эк­ Вивалентно объявлению его в Java как синхронизированного (рис. 9.2).

I

 

I

TrafficSensor

1

1

*

 

 

. .

Оповещает о прохождении транспортного средства.....

 

 

 

 

 

 

 

TrafficSensorController

 

 

 

 

vehicleCount:int

 

 

 

 

 

vehiclePassed() {concurrency-guarded}

 

 

getAndCLearCountO:int

1

{concurrencysguarded}

 

 

 

 

 

 

 

 

 

 

1

Получает

данные по учету движения транспорта .....

l

 

 

 

I

 

TrafficTransmiter

 

Рис. 9.2. Синхронизированные классы сенсоров движения транспорта

446

Глава 9.

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

Сколько угодно потоков MOryr обрашаться к охраняемым методам одного и тоГо

же объекта одновременно. Однако в какой-то момент только одному потоку

разрешено выполнение охраняемых методов объекта. Пока один поток ВЫПол­ няет охраняемые методы объекта, другие потоки ожидают до тех пор, пока по­

ток не завершит выполнение этих методов. Затем дЛя выполнения следующих

охраняемых методов объекта будет выбран произвольным образом другой по­

ток из числа ожидаюших. Такой механизм гарантирует однопоточное выполне­

ние охраняемых методов объекта.

МОТИВЫ

©Класс содержит методы, которые обновляют или задают значения в пере­ менных экземпляра или переменных класса.

©Метод манипулирует внешними ресурсами, которые поддерживают ТОлько одну операцию в какой-то момент времени.

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

©Не сушествует временного ограничения, которое требовало бы от метода

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

РЕШЕНИЕ

Гарантировать, чтобы операции, параллельное выполнение которых даст ошнеи­

бочный результат, не выполнялись бы параллельно. С этой целью методы, ПОдЛежашие параллельному выполнению, делаются охраняемыми (рис. 9.3).

Resource

safeOpl safeOp2

unsafeOpl {concurrency-guarded} unsafeOp2 {concurrency-guarded}

Рис. 9.3. Шаблон Single Threaded Execution

Класс, изображенный на рис. 9.3, имеет методы двух видов. Первый вид - не

охраняемые методы safeOp l , safeOp2 И Т.Д., которые MOryr безопасно "otf­

курентно выполняться. Второй вид - охраняемые методы unsafeOpl, unsafeOp2

и Т.Д. , которые не MOryr безопасно параллельно выполняться. Если различные

потоки одновременно вызывают охраняемые методы объекта Resou rce

, в ка­

кой-т

 

 

потоку разрешено выполнять так

 

ме­

о момент времени только одному

оЙ

 

тод. Остальные потоки

ЖДУТ,

пока этот поток не закончит свою работу.

 

 

 

 

 

Single Threaded Execution 8 447

РЕАЛИЗАЦИЯ

в языке Java охраняемые методы реализуются посредством объявления их син­ хронизированными. Для вызова синхронизированного метода может потребо­ ваться больше времени, чем для вызова несинхронизированного. Рассмотрим диаграмму взаимодействия (рис. 9.4).

lа: fooO {concurrency-guarded} 1

lа.l: doItO {concurrency-guarded} 1

Рис. 9.4. Факторинг синхронизации

Синхронизированный метод класса А вызывает метод do 1t класса В. Метод dolt является синхронизированным. Если метод dol t вызывается только син­

хронизирови анными методами класса А, то можно произвести оптимизацию

сделать метод dol t несинхронизированным. Он будет выполняться только

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

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

Такая оптимизация называется факторингом синхронизации. Факторинг син­ хронизации (synchronisation factoring) - это небезопасная оптимизация в том Смысле, что, если программа будут изменена так, что к методу dol t будут про­ Изводиться параллельные обращения, программа перестанет правильно функ­

ционировать. Поэтому, если разработчик решает, что оптимизацию стоит про­ иВодить «(вручную» , он должен внести комментарии в диаграммы проекта

Исходный текст кода, чтобы предупредить программистов о выполненной оп­ l'Имизации.

СУществуют компиляторы и NM, которые выполняют подобную оптимизацию

аВтоматическиSun. : например, NM, применяющие технологию HotSpot фирмы

Существуют компиляторы, которые могут осуществлять факторинг син­

Хронизации в определенных случаях. При использовании NM или компиля­ l'ора, который делает оптимизацию автоматически, лучше не делать подобную

Оптимизацию вручную.

448

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

СЛЕДСТВИЯ

© Если у класса есть методы, имеющие небезопасный доступ к переменным

или другим ресурсам, то все эти методы делаются охраняемыми, обеспечи_

вая таким образом безопасность потоков.

 

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

 

водительность может быть снижена. Это объясняется двумя причинами.

 

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

 

нительные затраты просто на вызов охраняемого метода. Другая ПРИ'lИна

 

состоит в том, что если метод объявлен охраняемым, а этого можно было бы

 

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

®

можно было бы избежать).

 

Если методы делаются охраняемыми, становится возможной взаимная бло­

 

 

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

 

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

 

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

 

поток ждет ресурс, к которому другой поток уже имеет монопольный дос­

 

ТУП, и поэтому оба потока будут ждать бесконечно, не получая доступа

 

к ожидаемому ресурсу.

 

 

Рассмотрим пример, представленный на рис. 9.5. Поток l а вызывает метод

 

foo объекта х , и одновременно поток вызывает метод bar объекта у. За­

 

тем поток l а вызывает метод bar объекта

у и ждет, пока поток закончит

 

свое обращение к этому методу. Поток

вызывает метод foo объекта

 

х и ждет, пока поток l а не завершит свое обращение к этому методу. С это­

 

го момента два потока взаимно заблокированы. Каждый ждет, пока другой

 

завершит свой вызов.

 

 

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

lа: foo() {concurrency-guarded}1

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

--.

lа.l: barO {concurrency-guarded}

+--

lЬ: fooO {concurrency-guarded}

Рис. 9.5. Взаимная блокировка

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