![](/user_photo/2706_HbeT2.jpg)
GrandM-Patterns_in_Java
.pdfScheduler - 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 не выполняется до тех пор, пока управляемый ресурс
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6472x1.jpg)
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 ;
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6473x1.jpg)
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 ожидает до тех пор,
пока не будет ни одного, ожидаюшего завершения, обрашения к методу
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6476x1.jpg)
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6477x1.jpg)
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6478x1.jpg)
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,
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6480x1.jpg)
.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