![](/user_photo/2706_HbeT2.jpg)
GrandM-Patterns_in_Java
.pdf![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6492x1.jpg)
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 показан класс, который можно использовать для согласованиЯ операций завершения процесса. Использование такого класса позволяет про-
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6493x1.jpg)
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6494x1.jpg)
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 з ;
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6495x1.jpg)
Этот шаблон известен также под названием Ехс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 |
моти в ы
©Объект использует много данных сразу.
©Данные получаются объектом-потребителем от объекта, создающего данные.
Объект, производящий данные, не управляет тем, когда объект-потребитель потребует новых данных.
©Если объекту-потребителю нужны данные, а они недоступны, это в какой-то мере отражается на производительности объекта-потребителя.
©По прошествии некоторого времени средняя скорость использования дан ных не может превысить скорость их создания.
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6498x1.jpg)
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 нужен только один буфер. Особая ситуация, когда требу-