GrandM-Patterns_in_Java
.pdfReadl'Nrite Lock • 489
Очевидно, что методы класса Bid используют объект ReadWri teLock для со гласования параллельных обращений. Сначала (перед чтением или записью значений) они вызывают соответствующий метод блокировки. По завершении они вызывают метод done объекта ReadWri teLock С целью разблокировки.
Класс ReadWri teLock более сложен. По мере прочтения его листинга можно заметить, что он уделяет основное внимание двум моментам.
1. Тщательно отслеживает информацию о состоянии, используя при этом ме ханизм, который совместим со всеми потоками.
2.Обеспечивает выполнение всех предварительных условий, преЖде чем вы
полнятся его методы блокировки.
Любые другие классы, отвечающие за проведение политики планирования, должны будуг позаботиться о реализации этих идей.
public class ReadWriteLock {
private int waitingForReadLock = О ;
private int outstandingReadLocks = О ;
private ArrayList waitingForWriteLock = new ArrayList ( ) ;
private Thread writeLockedThread;
Объект ReadWr i teLock использует эти переменные экземпляра для отслежи вания потоков, которые запросили или получили запрос на блокировку чтения или записи. Для отслеживания потоков, которые ожидают блокировки записи, он использует список, на который ссылается переменная wai t ingForWri teLock. Применение такого списка позволяет осуществлять блокировки запи си в том порядке, в котором выдавались соответствующие запросы.
Объект ReadWri teLock использует переменную wai ti ngForReadLock дЛя подсчета количества потоков, ожидающих блокировки чтения. Простой под счет является вполне достаточным, поскольку все потоки, ожидающие блоки ровки чтения, получат разрешение на них одновременно. Это значит, что не нужно отслеживать порядок запрашивания потоками блокировки чтения.
Объект ReadWri teLock использует переменную outstandingReadLocks дЛя подсчета количества блокировок чтения, которые были инициированы, но еще не освоБОЖдены соответствующими потоками, для которых они предназначались.
Объект ReadWriteLock использует переменную writeLockedThread для ССЬUIКИ на поток, у которого в данный момент блокирована запись. Если в данный мо
мент времени ни у одного потока не блокирована запись, то переменная wri teLockedThread содержит null. Имея переменную, ССЬUIающуюся на по ток, у которого была блокирована запись, объект ReadWri teLock знает, был ли поток активизирован с целью блокирования записи или по другой причине.
Метод readLock объекта ReadWriteLock действует следующим образом. Он
инициирует блокировку чтения и немедЛенно заканчивает выполнение, если
490 |
• Глава 9. Шаблоны проектирования ДЛЯ конкурирующих операций |
нет невыполненной блокировки записи. Блокировка чтения состоит в том, ЧТо
значение в переменной outstandingReadLocks увеличивается на единицу.
synchronized public void readLock ( ) throws InterruptedException if (writeLockedТhread != null) {
waitingForReadLock++ ; != while (writeLockedThread; null)
wait ( )
} / / while waitingForReadLock- ;
/ / i f outstandingReadLocks++ ;
/ / readLock ( )
Дальше - листинг метода writeLock. Как можно заметить, этот листинг боль ше, чем листинг метода readLock. Это объясняется тем, что wri teLock управ
ляет потоками и структурой данных. Код начинается с того, что проверяется
случай отсутствия невыполненных блокировок. Если нет невыполненных бло кировок, немедленно осуществляется блокировка записи. В противном случае текуший поток добавляется в список, который рассматривается методом done как очередь. Текущий поток ожидает до тех пор, пока метод done не разрешит ему блокировку записи. Затем метод wri teLock завершается, удаляя текущий поток из списка потоков, ожидающих блокировку записи.
public void writeLock () throws InterruptedException Thread thisThreadi
synchronized (this) {
if ( writeLockedThread--null
&& outstandingReadLocks==O) writeLockedТhread = Thread. currentThread ( ) ; return ;
/ / i f
thisThread = Thread . currentThread ( ) ; waitingForWriteLock . add (thisThread) ;
// s ynchroni zed ( this)
synchronized (thisThread)!- {
while (thisThread writeLockedThread) thisThread . wait ( ) ;
}/ / whi le
// synchronized ( thi sThread)
synchronized (this) {
waitingForWriteLock . remove (thisThread) ; } / / synchroni zed ( this)
/ / wri teLock
Read/Write lock • 491
Метод done завершает листинг классаД1IЯ ReadWriteLock. Потоки вызывают ме тод done объекта ReadWri teLock снятия блокировки, ранее произведен ной объектом ReadWriteLock. Метод done рассматривает три случая.
1. Существуют невыполненные блокировки чтения, наличие которых предпола гает, что нет невыполненных блокировок записи. Он снимает блокировку чтения посредством уменьшения на единицу переменной outstand i ngReadLock. Если больше нет невыполненных блокировок чтения и су Дшествуют1IЯ потоки, ожидающие блокировки записи, то блокируется запись
того потока, который дольше всех ждет этой блокировки. Затем ожи дающий поток активизируется.
2. Существует невыполненная блокировка записи. Метод done заставляет теку щий поток снять блокировку записи. Если имеются какие-либо потоки, ожидающие блокировки чтения, он инициирует блокировки чтения для всех этих потоков. Если нет невыполненнblX блокировок чтения и есть пото ки, ожидающие блокировки записи, этот метод блокирует запись того потока, который ожидает дольше всех, заставляя переменную wri teLockedThread ссылаться на этот, а не на текущий поток.
З. Нет невыполненных блокировок. Если нет невыполненных блокировок, это значит, что метод done был вызван ошибочно, поэтому он генерирует исключение I l legalStateException.
synchronized public void done ( )
if (outstandingReadLocks > О )
outstandingReadLocks- i
if ( outstandingReadLocks==O
" waitingForWriteLock . size ( » O) writeLockedThread=
(Тhread) waitingForWriteLock . get (O) writeLockedThreadif . notifyAll ( ) i
//
}else if (Thread . currentThread ( )
==writeLockedThread) {
if ( outstandingReadLocks==O
" waitingForWriteLock . size ( » O ) writeLockedThread=
(Thread) waitingForWriteLock . get (O)
writeLockedТhread. notifyAll ( ) i else . {
writeLockedТhread - null i
if |
(waitingForReadLock > О ) |
|
notifyAll () i |
/ / |
i f |
Producer-Consumeг • 495
Опишем роли, которые MOryr исполняться этими классами.
Producer. ЭкземruIЯРЫ классов в этой роли предоставляют объекты, использую щиеся объектами Consumer. ЭкземruIЯРЫ класса Producer создают объекты асинхронно по отношению к тем потокам, которые их используют. Это значит, что иногда объект Producer может создать объект в тот момент, когда все объ екты Consumerждутзаняты, обработкой других объектов. Экземпляры классов Producer не пока объект Consumer станет доступным, вместо этого они помещают созданные ими объекты в очередь и продолжают свою работу.
Queue. ЭкземruIЯРЫ классов в этой роли служат в качестве буфера для объектов, созданных экземплярами классов Producer. Экземпляры классов Producer помещают созданные ими объекты в экземпляр класса Queue. Объекты остают ся там до тех пор, пока объект Consumer не достанет их из объекта Queue.
Consumer. Экземпляры классов Consumer используют объекты, созданные объ ектами Producer. Они получают используемые ими объекты от объекта Queue. Если тот пуст, объект consume r, желающий получить от него объект, ожидает до тех пор, пока объект Producer не поместит объект в объект Queue.
Представленная на рис. 9.27 диаграмма взаимодействия демонстрирует взаимо действия между объектами, принимающими участие в шаблоне Producer-Con sumer.
:Producer
д: push(:Object) 1
....- ..1-__...,
В: pullO {сопсuпепсу-guагdеd I q.sizeO>O} r
,.с:::===::::::;,
:(опsumег
Рис. 9.27. Взаимодействие в шаблоне Producer-Consumeг
РЕАЛИЗАЦИЯ
Некоторые версии реализации шаблона Producer-Consumer накладывают огра Ничения на максимальный размер очереди. В таких версиях реализации рас сматривается специальный случай, когда очередь достигла своего максималь
ного размера, а поток производителя намеревается поместить в очередь еще
один объект. Тогда управление очередью обычно осуществляется при помощи шаблона Guarded Suspension, который вынуждает экземпляр класса Producer
Producer-Consumer _ 497
TroubleTicket tkt = null ;
myQueue . push (tkt) ;
// run ( )
// class C l ient
public class Dispatcher implements Runnable ( private Queue myQueue ;
public Dispatcher (Queue myQueue) this . myQueue = myQueue ;
}/ / constructor (Queue )
public void run ( ) (
TroubleTicket tkt = myQueue . pull ( ) ;
// run ( )
// class Di spatche r
Последний листинг содержит класс Queue.
public class Queue (
private ArrayList data = new ArrayList () ;
/* *
* Помещаем объе кт в конец очереди .
* @pa ram obj Объект , помещаемый в конец очереди .
* / |
|
|
|
|
synchronized public void push (TroubleTicket tkt) |
( |
|||
|
data . add (tkt) ; |
|
|
|
|
notify ( ) ; |
|
|
|
|
/ / push (TroubleTicket ) |
|
|
|
/ * * |
|
|
|
|
* |
Получаем TroubleTicke t , находящийся в |
начале |
очереди . |
|
* |
Если |
очередь пуста , ждем, пока в ней |
не появитс я объект . |
|
* / |
|
|
|
|
synchronized public TroubleTicket pull () |
( |
|
||
|
while |
(data . size ( ) == О) { |
|
|
try { wait ( ) ;
} catch (InterruptedException е) {
498 • Глава 9. Шаблоны проектирования ДЛЯ конкурирующих операций
} |
/ / try |
= (TroubleT1Cket) data . qet (O) ; |
// |
whi l e |
|
TroubleTicket tkt |
data . remove (O) ; return tkt ;
// pul l ( )
/* *
* Возвращаем количество претензий, находящихся * в этой очереди .
* /
public int size ( ) {
return data . size ( ) ;
} / / s i ze ( )
/ / class Queue
ШАБЛОНЫ ПРОЕКТИРОВАНИЯ, СВЯЗАННЫЕ
С ШАБЛОНОМ PRODUCER·CONSUMER
Guarded Suspension. Шаблон Producer-Consumer использует шаблон Guarded Suspension дЛЯ управления такой ситуацией, когда объект Consumer ожидает получения объекта из пустой очереди.
Pipe. Шаблон Pipe - это специальный случай шаблона Producer-Consumer, ко торый включает только один объект Producer и только один объект Consumer. Обычно шаблон Pipe ссылается на объект Producer как на источник данных и на объект Consumer как на приемник данных.
Scheduler. Шаблон Producer-Consumer может рассматриваться как специальная
форма шаблона Scheduler, политика планирования которого имеет две харак
•особенности:
•основана на доступности ресурса;
назначает ресурс для потока, но не нуждается в повторном получении кон
троля над ресурсом, если поток завершен, поэтому он может переназначитъ
ресурс другому потоку.терные