Главная » Java » Безопасность потоков и ThreadGroup

0

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

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

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

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

которую входит его поток-создатель. После завершения потока соответствующий  объект удаляется из группы.

public Thread(ThreadGroup group, String name)

Конструирует новый поток с заданным именем name (может быть равно null),

принадлежащий  конкретной группе.

После того как объект будет создан, вы уже не сможете изменить связанный с ним объект ThreadGroup. Чтобы узнать, какой группе принадлежит некоторый поток, следует вызвать его метод getThreadGroup. Кроме того, можно проверить, допустима ли модификация потока, — для этого вызовите его метод checkAccess. Этот метод возбуждает исключение SecurityException, если вы не можете модифицировать  поток, и просто завершается в противном случае (метод имеет тип void).

Группы потоков могут быть группами-демонами. Такие группы автоматически уничтожаются, когда в них не остается ни одного потока. То, что группа является группой-демоном,  никак не влияет на “демонизм” принадлежащих ей потоков или групп. Статус группы-демона определяет лишь то, что происходит с ней, когда группа становится пустой.

Группы потоков также могут использоваться для задания максимального приоритета потоков, входящих в нее. После вызова метода setMaxPriority, задающего максимальный приоритет группы, при любой попытке поднять приоритет потока выше указанного значения происходит его незаметное понижение до объявленного максимума. Вызов этого метода не влияет на существующие потоки. Чтобы быть уверенным в том, что приоритет некоторого потока всегда будет превышать приоритет всех остальных потоков группы, следует установить для него приоритет MAX_PRIORITY, после чего установить максимальный приоритет группы равным MAX_PRIORITY-1. Ограничение относится и к самой группе потоков — при попытке установить для нее максимальный приоритет, превышающий текущее значение, произойдет незаметное понижение затребованного приоритета:

static public void maxThread(Thread thr) { ThreadGroup grp = thr.getThreadGroup(); thr.setPriority(Thread.MAX_PRIORITY); grp.setMaxPriority(thr.getPriority()  – 1);

}

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

значения.

ThreadGroup содержит следующие конструкторы и методы:

public ThreadGroup(String name)

Создает новую группу ThreadGroup, принадлежащую  группе ThreadGroup текущего потока. Имена групп, как и имена потоков, не используются runtime-системой.  Если имя равно null, возбуждается исключение Null PointerException. Этим объекты ThreadGroup отличаются от объектов Thread, у которых наличие имени необязательно.

public ThreadGroup(ThreadGroup parent, String name)

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

public final String getName() Возвращает имя группы ThreadGroup. public final ThreadGroup getParent()

Возвращает родительскую группу ThreadGroup или null, если ее не существует.

public final void setParent(boolean daemon) Устанавливает “демонический” статус группы. public final boolean isDaemon()

Возвращает “демонический” статус группы.

public final synchronized void setMaxPriority(int  maxPri)

Устанавливает максимальный приоритет группы. public final int getMaxPriority()

Возвращает текущий максимальный приоритет группы. public final boolean parentOf(ThreadGroup g)

Проверяет, является ли текущая группа родителем группы g или же совпадает с ней. Лучше представлять себе этот метод в терминах “является частью”, так как группа является частью самой себя.

public final void checkAccess()

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

public final synchronized void destroy()

Уничтожает текущую группу типа ThreadGroup. Группа, в которой содержатся потоки, не может быть уничтожена; при попытке сделать это возбуждается исключение IllegalThreadStateException. Это означает, что метод destroy не может применяться для уничтожения всех потоков группы — это необходимо сделать вручную, воспользовавшись описанными ниже методами перечисления. Если в группу входят другие группы, то они также должны быть пустыми.

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

public synchronized int activeCount()

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

public int enumerate(Thread[]  threadsInGroup, boolean recurse)

Заполняет массив threadsInGroup ссылками на все активные потоки в группе до заполнения массива. Если значение recurse равно false, то перечисляются лишь потоки, непосредственно входящие в группу; если же оно равно true, то перечисляются все потоки в иерархии. Thread Group.enumerate, в отличие от ThreadGroup.activeCount, позволяет определить, включаете ли вы потоки в подгруппах или нет. Это значит, что вы можете получить разумную оценку для размера массива, необходимого для хранения результатов рекурсивного перечисления, однако для перечисления, не учитывающего подгрупп, такая оценка окажется завышенной.

public int enumerate(Thread[] threadsInGroup) Эквивалентно enumerate(threadsInGroup, true). public synchronized int activeGroupCount()

Аналогичен методу activeCount, однако подсчитывает не потоки, а группы, в том числе и

во всех подгруппах. “Активный” (active) в данном случае означает “существующий”. Неактивных групп не бывает; термин используется лишь для соблюдения единого стиля с activeCount.

public int enumerate(ThreadGroup[] groupsInGroup,   boolean       recurse)

Аналогичен методу enumerate для потоков, однако заполняет массив ссылками на объекты-группы типа ThreadGroup вместо объектов-потоков  Thread.

public int enumerate(ThreadGroup[] groupsInGroup)

Эквивалентно enumerate(groupsInGroup, true).

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

public final synchronized void stop()

Завершает все потоки в группе и во всех ее подгруппах. public final synchronized void suspend()

Приостанавливает  все потоки в группе и во всех ее подгруппах. public final synchronized void resume()

Возобновляет все потоки в группе и во всех ее подгруппах.

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

ThreadGroup для задания параметров потоков.

В классе Thread также имеется два статических метода для работы с группой, в которую входит текущий поток. Они представляют собой сокращенную запись для последовательного  вызова getCurrentThread, getThread Group и вызова метода для найденной группы:

public static int activeCount()

Возвращает количество активных потоков в группе, в которую входит текущий поток. public static int enumerate(Thread[] tarray)

Возвращает количество потоков в группе, в которую входит текущий поток.

Класс ThreadGroup также содержит метод, вызываемый при завершении потока, из-за неперехваченного  прерывания:

public void uncaughtException(Thread[] thr, Throwable exc)

Вызывается при завершении потока, вызванном неперехваченным  прерыванием.

Данный метод является открытым, так что вы можете переопределить его для обработки неперехваченных  прерываний по своему желанию. Реализация, принятая по умолчанию, вызывает метод uncaughtException  группы-родителя,  если таковая имеется, или метод Throwable.printStackTrace в противном случае. Например, при разработке графической оболочки было бы желательно отобразить содержимое стека в окне, вместо того чтобы просто вывести его в System.out, как это делает метод printSt a ckTrace. Вы можете переопределить uncaughtException  в своей группе, чтобы создать нужное окно и перенаправить в него содержимое стека.

9.14. Отладка потоков

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

public String toString()

Возвращает строковое описание потока, включающее его имя, приоритет и имя группы. public String countStackFrames()

Возвращает количество кадров стека в потоке.

public static void dumpStack()

Выводит в System.out содержание стека для текущего потока.

Также существует ряд отладочных средств для отслеживания состояния группы потоков. Следующие методы вызываются для объектов ThreadGroup и выдают информацию об их состоянии:

public String toString()

Возвращает строковое описание группы, включающее ее имя и приоритет. public synchronized void list()

Выводит в System.out список содержимого группы и ее подгрупп.

Глава 10

ПАКЕТЫ

Библиотека — это арсенал свободы.

Источник неизвестен

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

Пакеты позволяют группировать родственные интерфейсы и классы.

В интерфейсах и классах, входящих в пакет, могут использоваться популярные имена (вроде get или put), которые имеют смысл в данном контексте, но конфликтуют с теми же именами в других пакетах.

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

Давайте рассмотрим пример пакета для нашего класса атрибутов, использованного  в предыдущих главах. Назовем пакет attr. Каждый исходный файл, классы и интерфейсы которого принадлежат пакету attr, должен указывать на свою принадлежность к пакету объявлением package:

package attr;

Тем самым объявляется, что все классы и интерфейсы, определенные в этом исходном файле, являются частью пакета attr. Объявление package должно находиться в самом начале файла, до любых объявлений классов или интерфейсов; в файле может присутствовать всего одно объявление package. Имя пакета является неявным префиксом для всех имен типов, включенных в пакет.

Если фрагменту программы вне пакета понадобится обратиться к типам, входящим в пакет, у него имеется две возможности. Первая — указывать перед каждым именем типа префикс (имя пакета). Такой вариант будет вполне разумен, если вам приходится иметь дело всего с несколькими членами пакета.

Другая возможность доступа к типам пакета заключается в частичном или полном импортировании пакета. Программист, который захочет воспользоваться пакетом attr, может вставить следующую строку в начало своего исходного файла (после своего объявления package, но перед всем прочим):

import attr.*;

После этого он может обращаться к типам пакета просто по имени — например, Attributed. Пакет неявно импортирует сам себя, так что все, что определено в нем, становится доступным для всех остальных типов пакета.

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

Обращаться к типам по их полным именам — например, attr.Attributed и

lingua.Attributed.

Импортировать attr.Attributed или attr.*, после чего использовать простое имя

Attributed вместо attr.Attributed и полное имя lingua.Attributed.

Сделать обратное — импортировать lingua.Attributed или lingua.*, после чего использовать простое имя Attributed вместо lingua. Attributed и полное имя attr.Attributed.

Источник: Арнольд К., Гослинг Д. – Язык программирования Java (1997)

По теме:

  • Комментарии