Операционные системы "тонких" клиентов
Методическое пособие - Компьютеры, программирование
Другие методички по предмету Компьютеры, программирование
твенную кучу).
Но Java VM не выполняет действий по планированию нитей на выполнение. Для этого библиотечные методы Java обращаются к ОС, используя API той ОС, в среде которой работает Java VM.
Для нитей в Java предусмотрено управление приоритетами (методы getPriority(), setPriority()), однако, и здесь Java использует механизмы управления приоритетами ОС. Так, уже классическим для учебников по Java является пример аплета, в котором по экрану "наперегонки" движутся несколько объектов, движение каждого осуществляется в отдельной нити и с собственным приоритетом. Этот пример весьма наглядно демонстрируется в средах, например, OS/2 и Linux, но выглядит не очень убедительным в среде Windows 95/98, так как приоритет нити не слишком влияет на скорость движения объекта - примерно так, как показано на рисунке 13.5.
Рисунок 13.5 "Гонки" в разных операционных средах (движение справа налево).
Любая нить может быть сделана нитью-"демоном". Нить-"демон" продолжает выполняться даже после окончания той нити, в которой она была создана. Нить становится "демоном" при ее создании только в том случае, если она создается из нити-"демона". Программа может изменить состояния нити при помощи метода setDaemon() класса Thread. Выполнение любой программы Java VM начинает с единственной нити (в которой вызывается метод main()), и эта нить запускается не как "демон". Java VM продолжает существовать, пока не завершатся все нити не-"демоны".
В языке Java имеется также класс ThreadGroup - группа нитей, которая может управляться совместно.
Если в Java предусмотрены нити, то, естественно, должны быть предусмотрены и средства синхронизации и взаимного исключения при параллельной работе нитей. Основным средством синхронизации и взаимного исключения в Java является ключевое слово synchronized, которое может употребляться перед каким-либо программным блоком. Ключевое слово synchronized определяет невозможность использования программного блока двумя или более нитей одновременно. В Java synchronized-блоки называются мониторами и их фактическая тождественность мониторам Хоара, описанным в разделе 8.8 части I, очевидна. В зависимости от деталей способа употребления synchronized может работать как:
защищенная (guard - см. раздел 8.8 части I) процедура - в том случае, если synchronized-блок представляет собой целый метод, однако, в отличие от описанных нами guard-процедур одновременное вхождение в разные synchronized-методы возможно;
анонимные скобки критической секции - в том случае, если synchronized-блок является просто программным блоком;
скобки критической секции с защитой выбранного ресурса - в том случае, если после ключевого слова synchronized указывается в скобках ссылка на объект.
В первоначальной версии языка Java для класса Thread предусмотрены методы:
resume() - приостановить выполнение нити;
suspend() - возобновить выполнение нити;
yeld() - сделать паузу в выполнении нити, чтобы дать возможность выполниться другой нити;
join() - ожидать завершения нити.
Эти средства позволяют синхронизировать работу нитей, но в следующих версиях был (наряду со старыми средствами) введен новый, более стройный аппарат синхронизации и взаимного исключения.
Класс Object имеет три метода:
wait() - ожидать уведомления об этом объекте;
notify() - послать уведомление одной из нитей, ждущих уведомления об этом объекте;
notifyAll() - послать уведомление всем из нитям, ждущим уведомления об этом объекте.
Поскольку класс Object является корнем иерархии классов, объекты всех - стандартных и пользовательских - классов являются его подклассами и наследуют эти методы. Эти методы аналогичны операциям wait и signal, описанным нами в разделе 8.7 части I. Реализация, например, общего (с возможным значением, большим 1) семафора с использованием этих средств будет выглядеть следующим образом:
//** семафор реализуется в виде класса Semaphore
public class Semaphore
{
// значение семафора
private int Semaphore_value;
//** пустой конструктор семафора, по умолчанию начальное значение семафора - 0
public Semaphore()
{
this(0);
}
//** конструктор с параметром - начальным значением семафора,
// если задано отрицательное значение, устанавливается начальное значение 0
public Semaphore(int val)
{
if (val < 0) Semaphore_value = 0
else Semaphore_value = val;
}
//** V-операция. V- и P-операции объявлены synchronized,
// чтобы исключить одновременное выполнение их двумя или более нитями
public synchronized void V()
{
// возможно пробуждает нить, ожидающую у семафора
if (Semaphore_value == 0) this.notify();
// увеличивает значение семафора
Semaphore_value++;
}
//** P-операция
public synchronized void P() throws InterruptedException
// исключение может выбрасываться в wait
{
// если значение семафора равно 0, блокирует нить
while (counter == 0) this.wait();
// уменьшает значение семафора
Semaphore_value--;
}
}
В Java VM с каждым объектом связывается замок (lock) и список ожидания (wait set).
В спецификациях байт-кода Java имеются специальные команды monitorenter и monitorexit, устанавливающие и снимающие замок. Java VM, входя в synchronized-блок, пытается выполнить операцию установки замка и не продолжает выполнения нити, пока операция не будет выполнена. При выходе из synchronized-блока выполняется операция снятия замка.
Список ожидания используется методами wait(), notify(), notifyAll(). Он представляет собой список нитей, ожидающих уведомления о данном объекте. Названные операции работают с этим списком очевидным образом.
Следует отметить, что многопоточность, заложенная в языке Java, имеет большие перспективы. Изна