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

GrandM-Patterns_in_Java

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

Scheduler - 479

class Printer (

private Scheduler scheduler = new Scheduler ( ) i

public void print (JournalEntry j ) { try {

sCheduler . enter (j) i try (

finally

scheduler . done ( ) i

/ / try

catch (InterruptedException е) {

// try

}/ / class Printer

Каждый объект Printer использует объект Scheduler для планирования па­

раллельных обращений к его методу print, поэтому они распечатываются по­ следовательно и в соответствии с их временем поступления. Сначала вызывает­ ся метод enter объекта Scheduler, которому передается распечатываемый объект JournalEntry. Вызов не выполнится до тех пор, пока объект Sche­ duler не решит, что подошла очередь распечатать этот объект JournalEntry.

Метод print заканчивается обращением к методу done объекта Schedu ler.

Вызов метода done говорит объекту Scheduler о том, что объект Jour­ nalEntry распечатан, и может подойти очередь вывода на печать другого объ­ екта Journa lEntry.

Приведем исходный код для класса Scheduler:

public class Scheduler (

private Thread runningThreadi

Переменная runn ingThread устанавливается в nul l , если управляемый объек­

ТОКом, Scheduler ресурс не занят. Если ресурс занят, она содержит ссылку на по­ используюший этот ресурс.

private ArrayList waitingRequests = new ArrayList ( ) i private ArrayList waitingТhreads = new ArrayList ( ) i

I1нвариантом для этого класса является то, что запрос и соответствующий ему Поток находятся только в wai ti ngRequests и wai tingThread, пока обраще­

If!Ие к методу enter ожидает выполнения. Запросы, ожидающие выполнения,

соответствующие потоки содержатся в разных объектах ArrayList, что способ­

I:'rByeT принятию решения, какой запрос должен быть обработан следующим.

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

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

не освободится и объект Scheduler не примет решение, что ПОДОllUlа очередь

выполнения этого запроса.

public void enter (ScheduleOrdering з)

 

 

throws InterruptedException

 

Thread thisThread

= Thread . currentThread ( ) ;

11 Когда управляемый ресурс свободен ,

выполняется СИНХронизация

1 /

этого объекта, чтобы два параллельных вызова

11

метода enter не были выполнены одновременно .

synchronized (this)

{

 

 

if

(runningТhread == null) (

 

 

 

runningThread = thisThread;

 

 

 

return ;

 

 

 

 

I / if

 

 

 

waitingТhreads . add (thisThread) ;

 

 

waitingRequests . add (s) ;

 

 

11

synchroni zed ( this)

 

synchronized (thisThread)

 

 

11

ожидае т , пока вызов метода done

другим потоком не решит,

 

11

что пришла очередь этого потока .

 

 

while ( thisThread

!= runningТhread)

 

 

}

thisThread. wait ( ) ;

 

 

1 1 whi le

 

 

 

1 1

synchronized ( thisThread)

 

synchronized (this)

(

 

 

int i = waitingТhreads . indexOf (thisThread) ;

 

waitingТhreads . remove (i) ;

 

 

waitingRequests . remove (i) ;

 

11

11

synch roni zed ( this )

 

enter ( S chedu leOrde ring)

 

Вызов метода done указывает на то, что текущий поток завершил рабоТУ,

и управляемый ресурс освободился.

synchronized public void done ( )

i f (runningThread ! = Thread . currentThread ( »

throw new IllegalStateExcept1on ("Wrong Thread" ) ; int waitCount = waitingТhreads . Size ( ) ;

if (waitcount <= О ) ( runn1ngТhread = null ;

Scheduler - 481

else if (waitCount == 1) {

runninqThread = (Thread) waitinqТhreads . qet (O) ; waitinqТhreads . remove (O) ;

else {

- 1 ;

int next = waitCount

ScheduleOrderinq nextRequest ; nextRequest

= (ScheduleOrdering) waitinqRequests . get (next) ;

for (int i = waitCount-2 ; i>=O ; i-) {

ScheduleOrderinq r ;

r =

(ScheduleOrderinq) waitingRequests . qet (i) ;

if

(r . scheduleBefore (nextRequest) )

next = i ;

nextRequest = (ScheduleOrderinq)

waitinqRequests . qet (next) ;

/ / i f / / for

runningThread = (Thread) waitinqТhreads . qet (next) ; synchronized (runninqТhread) (

runninqТhread. notifyAll( ) ;

} / / synchronized ( runn ingThread)

// if wai tCount

// done ( )

// class Scheduler

:ля активизации потока метод done использует метод noti fyAl l , а не метод ot i fy, так как нельзя с уверенностью утверждать, что не существует другого

отока, также ожидающего получения права на блокировку объекта run­ ingThread. Если используется метод notify И существуют другие потоки,кже ожидающие получения права на блокировку объекта runningThread, то lетод notify может активизировать не тот поток, который нужен.

acc Scheduler не зависит от конкретного типа управляемого ресурса. Но он

Ребует, чтобы классы, описывающие операции над управляемым ресурсом, tализовывали интерфейс ScheduleOrdering. Если несколько операций ожи­ IllOт доступа к ресурсу, класс S cheduler использует интерфейс ScheduleOr­ ering для определения порядка выполнения операций. Листинг интерфейса chedul eOrdering:

public interface ScheduleOrderinq (

public boolean scheduleBefore (ScheduleOrderinq з ) ;

} / / interface Schedu leOrdering

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

и наконец, примерный код класса JournalEntry, который должен быть рас­

печатан классом Printer:

public class JournalEntry implements ScheduleOrdering {

private Date time = new Date () ;

/ * *

* Возвращает время создания этого JournalEntry . * / public Date getTime ( ) { return time ; }

/ * * * Возвращает t rue , если данный запрос должен * обрабатываться перед этим запросом . * / public boolean scheduleBefore (ScheduleOrdering з) {

if (з instanceof JournalEntry)

return getTime ( ) . before « (JournalEntrY) 8) . getTime (» ; return false i

/ / s CheduleBefore (ScheduleOrdering) / / c lass JournalEntry

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

Read/Write Lock. Некоторые реализации шаблона ReadfWrite Lock обычно

используют шаблон Schedu1er с целью осуществления качественного планиро­

вания.

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

СИНОПСИС

Разрешает объекту осушествлять параллельный доступ для операций чтения, но осуществляет монопольный доступ для операций записи.

КОНТЕКСТ

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

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

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

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

Шаблон Read/Write Lock позволяет избежать длительного ожидания при чте­ нии данных, разрешая параллельные операции чтения, но допуская только по­

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

На рис. 9.22 представлено несколько объектов пользовательского интерфейса, вызываюших методы getBid И setBid объекта предложенной цены. Перед тем как возвратить текушую предложенную цену, метод getBid ожидает до тех пор,

пока не будет ни одного, ожидаюшего завершения, обрашения к методу

484

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

setBid. Перед тем как изменить текущую преД)lОженную цену, метод setBid

ожидает завершения выполнения всех обращений к методам getBi d илli setBid. С целью возможности повторного использования объект ReadWri_ teLock инкапсулирует логику, координирующую выполнение методов getBid

И setBid.

Обращения метоАУ readLock ..

 

I :UserInterface

Обращения

 

метоАУ writeLock

 

 

 

ожидают выполнения, ecnи

 

ожидают выкполнения, еcnи

 

 

к

 

 

 

 

существуют невыполненные

1А: getBidO

 

 

В: setBid(bid)i

существуют невыполненные

 

 

обращении метоАУ writeLock

 

 

обращения

к

методам readLock

 

 

 

 

 

или еcnи об ращение

 

или еcnи обращениек

метоАУ

 

 

или writeLock

 

 

 

wrlteLock выполнилоськ , но за ним

 

 

одному

этих методов было

 

 

не поcnедовan ызов метода done.

 

 

 

 

 

 

вык полненоИ3, но за ни м

 

 

 

 

 

 

 

 

 

не п оcnед.

 

" .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

\г-_____---: 1

 

;]iri

It---------...7,

' OД' '"" .

 

 

 

 

 

 

 

L..-

__

....J

 

 

 

 

 

 

 

 

 

 

 

,r--

 

 

 

 

 

 

 

 

 

 

--r

---

 

 

 

 

 

 

1 A.l:readLock()

 

 

1А.2 done()

 

 

8.2 done() 1

 

 

8.1:writeLock() 1

 

 

 

 

 

 

 

 

L----------:ReadWritef:Lock

:1-------.1

 

 

 

 

 

 

 

 

Рис. 9.22. Взаимодействие ДЛЯ предложения цены на аукционе

 

 

 

Все обращения к методу readLock объекта ReadWri teLock выполняются не­ медленно, если нет каких-либо невыполненных блокировок записи. Блокировкако­ записи считается невыполненной в течение некоторого интервала времени, торый начинается с момента возврата метода wri teLock объекта ReadWri­ teLock И заканчивается моментом его освобождения соответствующим вызо­ вом метода done объекта ReadWriteLock. Обращения к методу readLock

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

Обращения к методу writeLock объекта ReadWriteLock выполняются немед­

, если не является истинным хотя бы одно из следующих утверждений:

ожидает выполнения предьщущий вызов метода wr i teLock;

предьщущее обращение к методу wri teLock завершилось, но не было соОТ­

ветствующего вызова метода done объекта ReadWri teLock;

имеются какие-либо выполняющиеся обращения к методу readLock оБЪ-ленно

екта ReadWri teLock или невыполненные блокировки чтения.

Если обращение к объекту ReadWriteLock производится В тот момент, когда какое-либо из вышеуказанных условий является истинным, оно не выполняеТ­ ся до тех пор, пока все указанные условия не станут false.

Read/Write Lock 485

МОТИВЫ для

Существует необходимость доступа чтения и записи информации о со­

©

стоянии объекта.

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

соперацией чтения не выполняются операции записи.

©В какой-то момент времени должна выполняться только одна операция за­ писи информации о состоянии объекта, это гарантирует правильность вы­ полнения этой операции.

©Предполагается наличие параллельно инициируемых операций чтения.

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

©Логика, предназначенная согласования операций чтения и записи,

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

РЕШЕНИЕ

Нужно организовать класс таким образом, что параллельные обращения к ме­ тодам, считывающим и сохраняющим информацию из его экземпляра, коорди­ нируются экземпляром другого класса. На рис. 9.23 показаны роли, исполняе­ мые классами в шаблоне ReadfWrite Lock.

Data getAttributel setAttributel

.. .

1

Использует

1

ReadWriteLock readLockO writeLockO doneO

Рис. 9.23. КлаССbl в шаблоне Read/Write Lock

!<ласс в роли Data имеет методы для чтения и записи информации своего эк­

Земпляра. Любому количеству потоков разрешено параллельно считывать ин­ формацию из экземпляра объекта Оа ta до тех пор, пока не появится поток, од-

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

новременно записывающий информацию. С другой стороны, только одна

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

гда не выполняются операции считывания. Объекты Dа ta должны согласовы_

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

неукоснительно выполнялись.

Блокировка чтения - это абстракция, которая используется объектами Data

для координации операций чтения. Методы чтения объекта Data не Произво­

дят выборку какой-либо информации до тех пор, пока они не выполнят блоки­

ровку чтения. С каждым объектом Data связан объект ReadWri teLock. Перед тем как один из его методов get начнет что-нибудь считывать, он вызывает ме­

тод readLock объекта ReadWri teLock, который осуществляет блокировку чте­

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

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

ReadWri teLock не будет осуществлять блокировку записи, пока существуют

какие-либо невыполненные блокировки чтения. Если при вызове метода readLock объекта ReadWri teLock имеются какие-либо невыполненные бло­ кировки записи, этот метод не будет выполнен до тех пор, пока все невыпол­ ненные блокировки записи не будут освобождены посредством обращений

кметоду done объекта ReadWriteLock. Во всех остальных случаях обращения

кметоду readLock объекта ReadWriteLock выполняются немедленно.

Когда метод get объекта Dа ta закончит считывание данных объекта, он вызы­ вает метод done объекта ReadWri teLock. Обращение к этому методу заставля­ ет текущий поток снять блокировку чтения.

Таким же образом объекты Dаta используют абстракцию блокировки записи

с целью согласования операций чтения. Методы set объекта Data не сохраняют

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

как один из методов set объекта Data запишет какую-либо информацию, он

вызывает метод wri teLock соответствующего объекта ReadWri teLock, кото­ рый выполняет для текушего потока блокировку записи. Пока поток обладает

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

данных объекта. Это объясняется тем, что объект ReadWri teLock осуществля­

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

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

ReadWri teLock имеются какие-либо невыполненные блокировки, он не будет выполнен до тех пор, пока все невыполненные блокировки не будут освобож­

дены посредством обращения к методу done объекта ReadWri teLock.

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

писи, не касаются порядка самого блокирования. Порядок блокировки чтенИЯ

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

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

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

Существует некоторая неопределенность в том случае, когда имеются обраше- ния сразу к двум методам, readLock и writeLock, объекта ReadWri teLock,

Readl'Nrite Lock 487

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

РЕАЛИЗАЦИЯ

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

Разные варианты политики планирования, представленные в данном описа­ нии шаблона Read/Write Lock, отдают предпочтение блокировке чтения. Это означает, что если некоторый вызов ожидает получения блокировки записи, поскольку существуют невыполненные блокировки чтения, то любые запро­ сы, желающие получить дополнительные блокировки чтения, в таких обстоя­ тельствах будуг удовлетворены немедленно. Подобная политика хороша для многих приложениЙ. Но иногда используются и другие приложения, где более уместна такая политика планирования, которая отдает предпочтение блоки­ ровкам записи.

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

СЛЕДСТВИЯ

©Шаблон Read/Write Lock координирует параллельные обращения к мето­ дам get И set объекта таким образом, чтобы обращения к методам set не мешали ни друг другу, ни обращениям к методам get.

©При наличии большого количества параллельных обращений к методам ge t объекта шаблон Read/Write Lock, применяемый с целью согласования этих обращений к методам get, может продемонстрировать в конечном счете лучшую способность к реагированию и производительность, чем ис­ пользуемый для той же цели шаблон Single Threaded Execution. Объясняется это тем, что шаблон Read/Write Lock разрешает параллельное выполнение

параллельных обращений к методам get объекта.

При наличии относительно небольшого количества параллельных обраще­

ний к методам get объекта применение шаблона Read/Write Lock приведет

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

®

Threaded Execution. Это объясняется тем, что шаблон Read/Write Lock за­

трачивает больше времени на управление отдельными обращениями.

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

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

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

ем записи (write starvation).

®Аналогичным образом, если политика планирования отдает предпочтеНие блокировкам чтения, тогда возможна такая ситуация, когда блокировки

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

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

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

ция называется зависанием чтения (read starvation).

Существуют подходы, которые позволяют избежать проблем с ситуациями Ta

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

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

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

ПРИМЕР КОДА

Приведем код, реализующий проект, который был рассмотрен в разделе «Кон­ текст» . Сначала - листинг достаточно простого класса Bid.

public class Bid { private int bid = О ;

private ReadWriteLock lockМanaqer = new ReadWriteLock ( ) ;

public int qetвid( ) throws InterruptedException { lockМanaqer . readLock ( ) ;

int bid - this . bid; lockМanaqer. done () ; return bid;

/ / getBid ( )

public void setвid (int bid) throws InterruptedException { lockМanaqer . writeLock () ;

if (bid > this . bid) this . bid = bid;

} / / i f

lockМanaqer . done ( ) ;

// setBid ( int )

// class Bid

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