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

GrandM-Patterns_in_Java

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

СИНОПСИС

Обеспечивает нормальное завершение потока или процесса, устанавливая флаг. Поток или процесс проверяют значение флага в стратегических точках

своего выполнения.

КОНТЕКСТ

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

Сервер создает информации о

потокдля продаже

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

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

Кроме того, сервер должен реагировать на административную команду, которая отключает весь сервер.

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

Взаимодействие начинается с вызова метода run объекта Ses s i on. Метод run сначала вызывает метод initial i ze объекта Sess ion. Затем он периодически вызывает метод sendTran sact ionsToC l ient объекта Po rtfol io. Он продол­ жает вызывать этот метод, пока метод i s I nter rupted объекта Ses sion воз­ вращает false. Метод i s I nterrupted объекта Session будет возвращать false до тех пор, пока не будет вызван метод interrupt объекта Ses s ion.

Нормальная последовательность событий, завершающая сеанс, начинается не с того потока, который вызвал метод run, а с другого. Этот другой поток ВЫЗЫ-

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

 

1

1A: runO

---+

 

 

 

1д.З: shutdown()

 

 

 

---+

 

 

 

 

 

 

 

 

18: interruptO

 

 

 

 

 

 

 

 

«self»

 

 

1д.2* [!s.islnterтupted()]: sendTransactionsToClientO

1

---+

 

 

1д.1: initialize

 

..._--_..._J......-,

 

 

 

 

 

 

:Portfolio

 

 

 

 

 

 

....

Завершение

п

оток

а

сервера

 

Р

 

с 9.28.

 

 

 

вает метод interrupt объекта Session. Тогда при следующем вызове объек­ том Ses s ion своего метода i s l nterrupted этот метод возвратит true, и метод run прекратит вызывать метод sendTransactionsToC l ient объекта Port­ folio. Затем он вызывает метод shutDown объекта Sess ion, который выпол­ няет все необходимые операции по закрытию.

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

МОТИВЫ

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

©Случай вынужденного завершения проuесса или потока без возможности проведения необходимых действий по завершению работы является неже­

лательным.

©При поступлении к потоку или процессу запроса на завершение вполне до­

пустимо, что перед завершением потока или процесса потребуется некотО­

рое время для очистки.

РЕШЕНИЕ

Для закрытия потока или процесса применяются две основные технологИИ.

Один способ состоит в том, чтобы завершить их сразу. Другой способ - запро­

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

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

потом - закрытия.

На рис. 9.29 показан класс, который можно использовать для согласованиЯ операций завершения процесса. Использование такого класса позволяет про-

Two-Phase Termination 8 501

цессу получать запрос на завершение и затем выполнять освобождение своих ресурсов перед непосредственным закрытием. Каждый поток процесса вызы­ вает метод i sShutdownReque s t ed в стратегических точках своего выполнения. Если этот метод возвращает true, поток выполняет действия по освобождению ресурсов и завершается. При закрытии « (смерти») всех потоков приложения происходит закрытие процесса.

Terminator -shutDоwпRеguеstеd:Ьооlеап - false +doShutDownO

+isShutdownReguestedO:boolean

Рис. 9.29. Завершение процесса

Завершение отдельного потока предполагает использование предназначенного специально для него флага. Такой флаг имеет каждый поток в Java, так как флаг является частью класса Thread. Ему присваивается значение true при вы­ зове метода interrupt потока. Значения флага можно получить, вызвав метод i s I n terrupted потока.

РЕАЛИЗАЦИЯ

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

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

Методы, задающие для флага завершения значение true, не нуждаются в син­

хронизации. Установка флага является идемпотентноЙ. Один или несколько потоков могут выполнять операцию (параллельно или не параллельно), и ре­ зультат все же будет соответствовать установленному в true значению флага за­ вершения.

СЛЕДСТВИЯ

© Использование шаблона Two-Phase Termination позволяет процессам и по­

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

® Использование шаблона Two-Phase Termination может задержать выполне­ ние процесса или потока на неопределенное время.

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

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

Ядро Java API не использует шаблон Two-Phase Тепniпаtiоп, однако в нем име­

ются средства ДЛЯ поддержки этого шаблона.

Для поддержки двухфазного завершения потоков класс Thread предостаВляет

метод interrupt, запрашивающий завершение потока. Кроме того, класс

Thread содержит метод i s lnterrupted, позволяющий потоку обнаруживать

запрос на его завершение.

Существуют методы, например, s leep, которые приводят поток в состояние

ожидания. Считается, что, если поток получил запрос на завершение, ожидая

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

методов, приведщих поток в состояние ожидания. Чтобы гарантировать свое­

временность закрытия потока, некоторые методы, заставляющие поток чего-то ожидать, генерируют исключение I nterruptedException, если поток ждет выполнения одного из таких методов в момент вызова его метода interrupt. Генерировать исключение InterruptedException могут, например, следую­ щие методы: Thread . s leep, Thread . j o in и Obj ect . wait.

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

Обратите внимание, что Java не предоставляет какой-либо возможности не­ посредственного определения или приема сигналов или прерываний от опе­ рационной системы, которые могут привести к закрытию процесса. Класс j ava . lang . Runtime имеет метод addShutdownHook, который может быть ис­ пользован ДЛЯ регистрации кода, который будет работать при завершении

NM. Документация, описывающая этот метод, обещает, что NM приложит

все усилия ДЛЯ работы кода. Но нет никакой гарантии, что он действительно

будет работать.

ПРИМЕР КОДА

Следующий листинг содержит код, который реализует проект, рассмотренный

в разделе « KOHTeKCT ):

public class Session implements Runnable ( private Thread myThread;

private Portfolio portfolio ; private Socket mySocket ;

public Session= (Socket з) myThread = new Thread (this) ; mySocket з ;

Two-Phase Termination 503

/ / cons tructor ( )

public void run ( ) {

initialize ( ) ;

while ( !myThread. interrupted (»

portfolio . sendTransactionsToClient (mySocket) ;

}/ / whi le

shutDown ( ) ;

/ / run ( )

/ * * * Запрос н а завершение этого сеанса . * /

public void interrupt ( ) {

myThread. interrupt () ;

}/ / interrupt ( )

/ * *

*Инициализирует этот объект .

*/

private void initialize ( )

}

/ / init ialized

/ * *

 

* Выполняем необходимые действия по освобождению ресурсов

 

* для этого объекта .

 

* /

private void shutDown ( )

 

/ / . . .

}

/ / shutDown ( )

} / / class Sess ion

Этот шаблон известен также под названием Ехсhапgе Buffering.

СИНОПСИС

Шаблон DоиЫе Buffering подготавливает данные к применению потребителя­

ми, позволяя избежать задержек во время использования объектом, благодаря

синхронному генерированию данных.

КОНТЕКСТ

Предположим, что нужно сопровождать ПО, которое загружает все транзакuии системы торговых терминалов, выполненные за день, в базу данных, поддер­ живающую хранилище данных (warehouse). Задача программиста - уменьшить количество времени, необходимого для загрузки транзакций системы торговых терминалов в базу данных.

Оказывается, что серьезным препятствием повыщению производительности является то, что программа тратит очень много времени на чтение файлов, со­ держащих записи транзакций. Для буферизации считываемых записей программа использует экземпляр класса j ava . i o . BufferedInputStream. Чтобы умень­ шить количество необходимых операций чтения, можно попытаться увеличить размеры буфера, используемого объектом BufferedInputStream.

В результате увеличения размеров буфера программа затрачивает меньше вре­

мени на чтение записей транзакций системы терминалов, но все еще ожидает

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

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

данных. Пока некоторые транзакции системы торговых терминалов добав.r.я­

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

уже считаны к тому времени, когда предыдущие транзакции системы термина­

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

пока будут считаны следующие транзакuии.

Чтобы решить эту проблему (когда программадолжна ожидать чтения транзакuий

системы терминалов), создают класс, похожий на класс BufferedInputStream.

Новый класс называется DoubleBufferedInputStream И будет использовать

два буфера вместо одного. При первом вызове одного из методов чтения этого

класса синхронно заполняется один из его буферов. С этого момента каждЫЙ буфер может исполнять одну из двух ролей.

Одна роль называется активный буфер; другая - резервный буфер. Буфер, кото­ рый синхронно заполняется во время вызова первой операции чтения, сначала

DoubIe Buffering 8 505

является активным, а второй буфер - резервным. Как только активный буфер

заполнится, происходит следующее:

метод read считывает байты из активного буфера;

в то же самое время байты асинхронно считываются из основного входного

потока и поступают в резервный буфер.

Когда активный буфер пуст и асинхронное заполнение резервного буфера за­ вершено, эти два буфера меняются ролями. Если чтение байтов в резервный буфер еще не завершилось, обмен ролями откладывается до того момента, пока не закончится чтение. После того как произошел обмен ролями, метод read считывает байты из активного буфера, а резервный буфер заполняется байта­ ми, асинхронно считываемыми из основного входного потока. Эти взаимодей­

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

1.1: activeBuffer :- buffer1

 

 

 

1.2: reserveBuffer :- buffer2

 

 

 

Ьuffег1:ЬytеП

 

buffer2:byteП

1.4/2.1.1: tmp :- activeBuffer

 

2.1 [activeBufferIsEmpty]: fiLlO

 

 

 

2.1.2: activeBuffer :- reserveBuffer

 

2.1.3: reserveBuffer :- tmp

1.3: read(activeBuffer)

 

2.2: read(activeBuffer)

1: readO

 

 

2: readO

 

 

 

1.4: read(reserveBuffer)

 

 

 

2.1.4: read(reserveBuffer)

Рис. 9.30. Взаимодействие ДЛЯ класса DoubIeBufferedlnputStream

моти в ы

©Объект использует много данных сразу.

©Данные получаются объектом-потребителем от объекта, создающего данные.

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

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

©По прошествии некоторого времени средняя скорость использования дан­ ных не может превысить скорость их создания.

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

РЕШЕНИЕ

Поведение некоторых объектов, использующих данные, таково, что они ис­

пользуют все данные одного буфера, а потом им не требуются данные из друго­ го буфера в течение неопределенного времени. Если это время в целом БОЛЬше

времени, необходимого дЛя повторного заполнения буфера, то можно повтор­

но использовать тот же самый буфер. Но если объект-потребитель не использу­

ет сразу все данные, наХОдЯщиеся в буфере, или не ждет достаточно долго, что

позволяет повторно заполнить буфер, то потребуется не один буфер.

На рис. 9.31 представлено взаимодействие между объектами, принимающими участие в шаблоне ОоиЫе ButТering. Объект DataProvider предоставляет данные

объекту, который вызывает его метод getData. Объекты bufferl и buffer2

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

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

когда они возвращаются методом getData. Объект DataSource отвечает за вы­

борку и создание данных.

--+

1.1: activeBuffer :- buffer1 1.2: reserveBuffer :- buffer2 2.1 [activeBufferIsEmpty): fiLL() 1.4/2.1.1: tmp :- activeBuffer 2.1.2: activeBuffer :- reserveBuffer 2.1.3: reserveBuffer :- tmp

1.3: fiIIBuffer(activeBuffer)--+

1:--getDataO+ 1: getDataO

2.2: filIBuffer(activeBuffer)

1.4: fillBuffer(reserveBuffer) 2.1.4: fiIIBuffer(reserveBuffer)

Рис. 9.31. Взаимодействие при двойной буферизации

Опишем эти взаимодействия.

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

1.1.Объект bufferl становится активным буфером.

1.2.Объект buffer2 становится резервным буфером.

1.3.Активный буфер синхронно заполняется.

1.4.Резервный буфер асинхронно заполняется. Этот вызов метода за­

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

DoubIe Buffering 507

2.Запрос на получение новых данных. Если в активном буфере находится достаточное количество данных, запрос удометворяется с помощью этих данных. В противном случае нужны дополнительные данные и вы­ полняется пункт 2.1. После завершения выполнения пункта 2.1 стано­ вятся доступными дополнительные данные, с помощью которых удов­ летворяется запрос.

2.1. Этот шаг выполняется только в том случае, если активный буфер пуст. При этом должно гарантироваться, что резервный буфер, который будет использоваться в качестве активного, заполнен, или заполнен настолько, сколько нужно для получения данных.

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

2.1.2, 2.1.3. Эти шаги являются завершающими при перемене ролей активного и резервного буферов. После шага 2.1.3 буфер, который был резервным перед шагом 2.1.1, ста­ новится активным, а буфер, который был активным, становится резервным.

2.1.4.Резервный буфер асинхронно заполняется. Вызов этого метода завершается сразу, не ожидая заполнения ре­ зервного буфера. За заполнение резервного буфера отвечает отдельный поток.

РЕАЛИЗАЦИЯ

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

М н о го ч и сл е н н ы е буфе р ы

Одно из предположений, лежащих в основе шаблона DoubIe Buffering, гласит, что с течением времени средняя скорость производства данных по меньшей мере становится равной скорости их использования. Некоторые приложения иногда могуг много раз запрашивать данные в течение непродолжительного времени, а затем какое-то время вообще ничего не запрашивать. В подобных

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

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

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

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

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

Уп р а вл е н и е и с кл ю ч и тел ь н о й с итуа ц и е й

Асинхронные операции выполняются с целью заполнения резервных буферов

данными до того, как эти данные могут понадобиться. Такие операции могут

генерировать исключения, которые требуют специальной обработки. С Обра­

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

Проблема состоит в том, что эти исключения генерируются в потоке, который,

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

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

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

их в потоке, запрашивающем данные. Однако при простой повторной генера­

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

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

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

Пример, рассмотренный в разделе «Пример кода», включает код, который об­ рабатывает исключения именно таким образом.

СЛЕДСТВИЯ

© Использование шаблона DoubIe Buffering позволяет избегать таких ситуа­ ций, когда приложение должно ждать получения новых данных, которые

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

Использование шаблона ОоиЫе Buffering приводит к увеличению объема

 

памяти, используемого программой, поэтому она работает быстрее.

®Шаблон ОоuЫе Buffering представляет собой оптимизацию, которая услож­

няет программу.

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

Java Swing использует шаблон ОоиЫе BufТering. Большаяj часть компонентов

Swing GUI представляет собой подклассы класса avax . swing. JComponent.

Класс JComponent имеет метод setDoubleBuffered. Этот метод используется

для включения и выключения двойной буферизации.

В разделе «Решение» описан специальный случай, когда при реализации шаб­ лона ОоиЫе BufТering нужен только один буфер. Особая ситуация, когда требу-

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