К. Арнольд, Д. Гослинг - Язык программирования Java (1160779), страница 37
Текст из файла (страница 37)
Используя этот метод, управляющий условием поток извещает все ожидающие потоки об изменении условия. Потоки,которые возобновляются лишь после выполнения данного условия, могут вызывать одну из разновидностей wait.Все эти методы реализованы в классе Object. Тем не менее они могут вызываться только из синхронизированных фрагментов, с использованиемблокировки объекта, в котором они применяются. Вызов может осуществляться или непосредственно из такого фрагмента, или косвенно — изметода, вызываемого в фрагменте.
Любая попытка обращения к этим методам для объектов за пределами синхронизированных фрагментов, длякоторых действует блокировка, приведет к возбуждению исключения IllegalMonitorState Exception.9.5. Планирование потоковJava может работать как на однопроцессорных, так и на многопроцессорных компьютерах, в однопоточных и многопоточных системах, так что вотношении потоков даются лишь общие гарантии.
Вы можете быть уверены в том, что исполнимый (runnable) поток с наивысшим приоритетомбудет работать и что все потоки с тем же приоритетом получат некоторую долю процессорного времени. Функционирование потоков с низшимприоритетом гарантируется лишь в случае блокировки всех потоков с высшим приоритетом. /Читателю следует отличать блокировку объекта(lock), о которой говорилось выше, от блокировки потока (block). Терминология, сложившаяся в отечественной литературе, может статьисточником недоразумений. - Примеч.
перев./ На самом деле не исключено, что потоки с низшим приоритетом будут работать и без такихрешительных мер, но полагаться на это нельзя.Поток называется заблокированным, если он приостановлен или выполняет заблокированную функцию (системную или функцию потока). Вслучае блокировки потока Java выбирает исполнимый поток с наивысшим приоритетом (или один из таких потоков, если их несколько) иначинает его выполнение.Runtime-система Java может приостановить поток с наивысшим приоритетом, чтобы дать поработать потоку с тем же приоритетом, — этоозначает, что все потоки, обладающие наивысшим приоритетом, со временем выполняются.
Тем не менее это вряд ли можно считать серьезнойгарантией, поскольку “со временем” — понятие растяжимое. Приоритетами следует пользоваться лишь для того, чтобы повлиять на политикупланирования для повышения эффективности. Не стоит полагаться на приоритет потоков, если от этого зависит правильность работы алгоритма.Начальный приоритет потока совпадает с приоритетом того потока, который создал его. Для установки приоритета используется методsetPriority с аргументом, значение которого лежит между константами MIN_PRIORITY и MAX_PRIORITY класса Thread. Стандартный приоритет дляпотока по умолчанию равен NORM_PRIORITY. Приоритет выполняемого потока может быть изменен в любой момент.
Если потоку будет присвоенприоритет ниже текущего, то система может запустить другой поток, так как исходный поток может уже не обладать наивысшим приоритетом.Метод getPriority возвращает приоритет потока.В общем случае постоянно работающая часть вашего приложения должна обладать более низким приоритетом, чем поток, занятый обработкойболее редких событий — например, ввода информации пользователем.
Скажем, когда пользователь нажимает кнопку с надписью STOP, он ждет,что приложение немедленно остановится. Если обновление изображения и ввод информации осуществляются с одинаковым приоритетом и вовремя нажатия кнопки происходит вывод, на то, чтобы поток ввода смог среагировать на нажатие кнопки, может потребоваться некотороевремя. Даже несмотря на то, что поток вывода обладает более низким приоритетом, он все равно будет выполняться большую часть времени,поскольку поток пользовательского интерфейса будет заблокирован в ожидании ввода.
С появлением введенной информации потокпользовательского интерфейса заставит поток вывода среагировать на запрос пользователя. По этой причине приоритет потока, которыйдолжен выполняться постоянно, устанавливается равным MIN_PRIORITY, чтобы он не поглощал все доступное процессорное время.Несколько методов класса Thread управляют планировкой потоков в системе:public static void sleep(long millis) throws InterruptedExceptionПриостанавливает работу текущего потока как минимум на указанное число миллисекунд.
“Как минимум” означает, что не существует гарантийвозобновления работы потока точно в указанное время. На время возобновления может повлиять планировка потоков в системе, гранулярностьи точность системных часов, а также ряд других факторов.public static void sleep(long millis, int nanos) •• throws InterruptedExceptionПриостанавливает работу текущего потока как минимум на указанное число миллисекунд и дополнительное число наносекунд. Значениеинтервала в наносекундах лежит в диапазоне 0–999999.public static void yield()Текущий поток передает управление, чтобы дать возможность работать и другим исполняемым потокам.
Планировщик потоков выбирает новыйпоток среди исполняемых потоков в системе. При этом может быть вызван поток, только что уступивший управление, если его приоритетокажется самым высоким.Приведенный ниже пример демонстрирует работу yield. Приложение получает список слов и создает потоки, предназначенные для выводаотдельного слова в списке. Первый параметр приложения определяет, должен ли каждый поток передавать управление после каждого вызоваprintln; значение второго параметра равно количеству повторений слова при выводе.
Остальные параметры представляют собой слова,входящие в список:class Babble extends Thread {static boolean doYield; // передавать управление другим потокам?Static int howOften;// количеств повторов при выводеString word;// словоBabble(String whatToSay) {word = whatToSay;}public void run() {for (int i = 0; i << howOften; i++) {System.out.println(word);if (doYield)yield(); // передать управление другому потоку}}public static void main(String[] args) {howOften = Integer.parseInt(args[1]);doYield = new Boolean(args[0]).booleanValue();}}// создать поток для каждого слова и присвоить ему// максимальный приоритетThread cur = currentThread();cur.setPriority(Thread.MAX_PRIORITY);for (int i = 2; i << args.length; i++)new babble(args[i]).start();Когда потоки работают, не передавая управления друг другу, им отводятся большие кванты времени — обычно этого бывает достаточно, чтобызакончить вывод в монопольном режиме.
Например, при запуске программы с присвоением doYield значения false:Babble false 2 Did DidNotрезультат будет выглядеть следующим образом:DidDidDidNotDidNotЕсли же каждый поток передает управление после очередного println, то другие потоки также получат возможность работать. Если присвоитьdoYield значение true:Babble true 2 Did DidNotто остальные потоки также смогут выполняться между очередными выводами и, в свою очередь, будут уступать управление, что приведет кследующему:DidDidNotDidDidNotПриведенные выше результаты являются приблизительными.
При другой реализации потоков они могут быть другими, хотя даже приодинаковой реализации разные запуски программы могут дать разные результаты. Однако при любой реализации вызов yield повышает шансыдругих потоков в споре за процессорное время.9.6. Взаимная блокировкаЕсли вы имеете дело с двумя потоками и с двумя блокируемыми объектами, может возникнуть ситуация взаимной блокировки (deadlock), прикоторой каждый объект дожидается снятия блокировки с другого объекта. Представим себе, что объект X содержит синхронизированный метод,внутри которого вызывается синхронизированный метод объекта Y, который, в свою очередь, также содержит синхронизированный метод длявызова синхронизированного метода объекта X.
Каждый объект ждет, пока с другого объекта не будет снята блокировка, и в результате ни одиниз них не работает. Подобная ситуация иногда называется “смертельными объятиями” (deadly embrace). Рассмотрим сценарий, в соответствии скоторым объекты jareth и cory относятся к некоторому классу Friend ly:1. Поток 1 вызывает синхронизированный метод jareth.hug. С этого момента поток 1 осуществляет блокировку объекта jareth.2. Поток 2 вызывает синхронизированный метод cory.hug.
С этого момента поток 2 осуществляет блокировку объекта cory.3. Теперь cory.hug вызывает синхронизированный метод jareth.hugBack. Поток 1 блокируется, поскольку он ожидает снятия блокировки с cory (внастоящее время осуществляемой потоком 2).4. Наконец, jareth.hug вызывает синхронизированный метод cory.hugBack. Поток 2 также блокируется, поскольку он ожидает снятия блокировкис jareth (в настоящее время осуществляемой потоком 1).Возникает взаимная блокировка — cory не работает, пока не снята блокировка с jareth, и наоборот, и два потока навечно застряли в тупике.Конечно, вам может повезти, и один из потоков завершит весь метод hug без участия второго.