Философия Java3
Шрифт:
В Java SE5 появилась новая версия sleep, оформленная в виде метода класса Timellnit; она продемонстрирована в приведенном примере. Она делает программу более наглядной, поскольку вы можете указать единицы измерения продолжительности задержки. Класс Timellnit также может использоваться для выполнения преобразований, как будет показано далее в этой главе.
На некоторых платформах задачи выполняются в порядке «идеального распределения» — от 0 до 4, затем снова от 4 до 0. Это вполне логично, поскольку после каждой команды вывода задача переходит в состояние ожидания, что позволяет планировщику потоков переключиться на другой поток. Тем не менее такое поведение зависит от базовой реализации потокового механизма, поэтому полагаться на него нельзя. Если вам потребуется
Приоритет
Приоритет (priority) потока сообщает планировщику информацию об относительной важности потока. Хотя порядок обращения процессора к существующему набору потоков и не детерминирован, если существует несколько приостановленных потоков, одновременно ожидающих запуска, планировщик сначала запустит поток с большим приоритетом. Впрочем, это не значит, что потоки с младшими приоритетами не выполняются вовсе (то есть тупиковых ситуаций из-за приоритетов не возникает). Потоки с более низкими приоритетами просто запускаются чуть реже.
В подавляющем большинстве случаев все потоки должны выполняться со стандартным приоритетом. Любые попытки манипуляций с приоритетами обычно являются ошибкой.
Следующий пример демонстрирует использование приоритетов. Приоритет существующего потока читается методом getPriority и задается методом setPriority:
//• concurrency/Si mplePri ori ti es.java
// Использование приоритетов потоков.
import java.util.concurrent.*.
public class SimplePriorities implements Runnable { private int countDown = 5;
private volatile double d; // Без оптимизации продолжение &
private int priority; public SimplePriorities(int priority) { this.priority = priority;
}
public String toStringO {
return Thread.currentThreadО + "; " + countDown;
}
public void runО {
Thread.currentThreadO.setPriority(priority); while(true) {
// Высокозатратная, прерываемая операция; for(int 1=1: i < 100000; i++) {
d += (Math.PI + Math.E) / (double)i; ifCi % 1000 == 0)
Thread.yieldO;
}
System.out.printin(this); if(--countDown == 0) return;
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPoolО; for(int i = 0; i < 5; i++) exec.execute(
new SimplePriorities(Thread.MIN_PRIORITY));
exec.execute(
new SimplePriorities(Thread.MAX_PRIORITY)); exec.shutdownO;
}
} /* Output:
Thread[pool-l-thread-6.10.main]: 5 ThreadEpool-l-thread-6.10.main]: 4 ThreadEpool-l-thread-6.10.main]: 3 ThreadEpool-l-thread-6.10.main]: 2 ThreadEpool-l-thread-6.10.main]: 1 ThreadEpool-l-thread-3.1.main]: 5 ThreadEpool-l-thread-2.1.main]: 5 ThreadEpool-1-thread-l.1.main]: 5 ThreadEpool-l-thread-5.1.main]: 5 ThreadEpool-l-thread-4.1.main]: 5
*///:-
В этой версии метод toStringO переопределяется и использует метод Thread. toString, который выводит имя потока (его можно задать в конструкторе, но здесь имена автоматически генерируются в виде pool-1-thread-l, pool-l-thread-2 и т. д.), приоритет и группу, к которой принадлежит поток. Переопределенная версия toString также выводит обратный отсчет, выполняемый задачей. Обратите внимание: для получения ссылки на объект Thread, управляющий задачей, внутри самой задачи, следует вызвать метод Thread.currentThreadO.
Мы видим, что приоритет последнего потока имеет наивысший уровень, а все остальные потоки находятся на низшем уровне. Учтите, что приоритет задается в начале выполнения run; задавать его в конструкторе бессмысленно, потому что Executor в этот момент еще не начал выполнять задачу.
В метод run были добавлены 100 ООО достаточно затратных операций с плавающей запятой, включая суммирование и деление с числом двойной точности double. Переменная d
была отмечена как volatile, чтобы компилятор не применял оптимизацию. Без этих вычислений вы не увидите эффекта установки различных приоритетов (попробуйте закомментировать цикл for с вычислениями). В процессе вычислений мы видим, что планировщик уделяет больше внимания потоку с приоритетом MAX_PRI0RITY (по крайней мере, таково было поведение программы на машине под управлением Windows ХР). Несмотря даже на то, что вывод на консоль также является «дорогостоящей» операцией, с ним вы не увидите влияние уровней приоритетов, поскольку вывод на консоль не прерывается (иначе экран был бы заполнен несуразицей), в то время как математические вычисления, приведенные выше, прерывать допустимо. Вычисления выполняются достаточно долго, соответственно, механизм планирования потоков вмешивается в процесс и чередует потоки, проявляя при этом внимание к более приоритетным. Тем не менее для обеспечения переключения контекста в программе периодически выполняются команды yield.В пакете JDK предусмотрено 10 уровней приоритетов, однако это не слишком хорошо согласуется с большинством операционных систем. К примеру, в Windows имеется 7 классов приоритетов, таким образом, их соотношение неочевидно (хотя в операционной системе Sun Solaris имеется 231 уровней). Переносимость обеспечивается толх^о использованием универсальных констант МАХ_РRIORITY, NORM.PRIORITY и MIN_PRI0RITY.
Передача управления
Если вы знаете, что в текущей итерации run сделано все необходимое, вы можете подсказать механизму планирования потоков, что процессором теперь может воспользоваться другой поток. Эта подсказка (не более чем рекомендация; нет никакой гарантии, что планировщик потоков «прислушается» к ней) воплощается в форме вызова метода yield. Вызывая yield, вы сообщаете системе, что в ней могут выполняться другие потоки того же приоритета.
В примере LiftOff метод yield обеспечивает равномерное распределение вычислительных ресурсов между задачами LiftOff. Попробуйте закомментировать вызов Thread.yield в Lift0ff.run и проследите за различиями. И все же, в общем случае не стоит полагаться на yield как на серьезное средство настройки вашего приложения.
Потоки-демоны
Демоном называется поток, предоставляющий некоторый сервис, работая в фоновом режиме во время выполнения программы, но при этом не является ее неотъемлемой частью. Таким образом, когда все потоки не-демоны заканчивают свою деятельность, программа завершается. И наоборот, если существуют работающие потоки не-демоны, программа продолжает выполнение. Существует, например, поток не-демон, выполняющий метод main.
//: concurrency/SimpleDaemons.java
// Потоки-демоны не препятствуют завершению работы программы
import java.util.concurrent.*.
import static net mindview.util.Print.*;
public class SimpleDaemons implements Runnable { public void run { try {
while(true) {
TimeUni t.MILLISECONDS.sieep(100). print(Thread.currentThread + " H + this);
}
} catch(InterruptedException e) {
printC'sleepO interrupted").
}
}
public static void main(String[] args) throws Exception { for(int i = 0. i < 10; i++) {
Thread daemon = new Thread(new SimpleDaemonsO).
daemon setDaemon(true); // Необходимо вызвать перед startO
daemon. startO;
}
printCBce демоны запущены"). TimeUnit.MILLISECONDS sleep(175);
}
} /* Output: Все демоны запущены
Thread[Thread-0.5.main] SimpleDaemons@530daa Thread[Thread-1.5.main] SimpleDaemons@a62fc3 Thread[Thread-2.5.main] SimpleDaemons@89ae9e Thread[Thread-3,5,main] SimpleDaemons@1270b73 Thread[Thread-4.5.main] SimpleDaemons@60aeb0 Thread[Thread-5.5.main] SimpleDaemons@16caf43 Thread[Thread-6.5.main] SimpleDaemons@66848c Thread[Thread-7.5.main] SimpleDaemons@8813f2 Thread[Thread-8.5.main] SimpleDaemons@ld58aae Thread[Thread-9.5.main] SimpleDaemons@83cc67