Приоритеты потоков

Приоритеты потоков используются планировщиком потоков для принятия решений о том, когда каждому из потоков будет разрешено работать. Теоретически высокоприоритетные потоки получают больше времени процессора, чем низкоприоритетные. Практически объем времени процессора, который получает поток, часто зависит от нескольких факторов помимо его приоритета. (Например, то, как операционная система реализует многозадачность, может влиять на относительную доступность времени процессора). Высокоприоритетный поток может также выгружать низкоприоритетный. Например, когда низкоприоритетный поток работает, а высокоприоритетный собирается продолжить свою прерванную работу (в связи с приостановкой или ожиданием завершения операции ввода-вывода), то последний выгружает низкоприоритетный поток.

Теоретически потоки с равным приоритетом должны получать равный доступ к центральному процессору. Но вы должны быть осторожны. Помните, что Java спроектирована для работы в широком спектре сред. Некоторые из этих сред реализуют многозадачность принципиально отлично от других. В целях безопасности потоки, которые разделяют один и тот же приоритет, должны получать управление в равной степени. Это гарантирует, что все потоки получат возможность выполняться в среде операционных систем с не вытесняющей многозадачностью. На практике, даже в средах с не вытесняющей многозадачностью большинство потоков все-таки имеют шанс выполняться, поскольку большинство потоков неизбежно сталкиваются с блокирующими ситуациями, такими как ожидание ввода-вывода. Когда подобное случается, заблокированный поток приостанавливается, и остальные потоки могут работать. Но если вы хотите добиться гладкой многопоточной работы, то не должны полагаться на это. К тому же некоторые типы задач интенсивно нагружают процессор. Такие потоки захватывают процессор. Потокам такого типа вы должны передавать управление от случая к случаю, чтобы дать возможность выполняться другим.

Чтобы установить приоритет потока, используйте метод setPriority (), который является членом класса Thread. Так выглядит его общая форма:

final void setPriority(int уровень)

Здесь уровень специфицирует новый уровень приоритета для вызывающего потока. Значение уровень должно быть в пределах диапазона от MIN_PRIORITY до MAX_PRIORITY. В настоящее время эти значения равны соответственно 1 и 10. Чтобы вернуть потоку приоритет по умолчанию, укажите NORM_PRlORITY, который в настоящее время равен 5. Эти приоритеты определены как статические финальные (static final) переменные в классе Thread.

Вы можете получить текущее значение приоритета потока, вызвав метод getPriority () класса Thread, как показано ниже:

final int getPriority()

Реализации Java могут иметь принципиально разное поведение в том, что касается планирования потоков. Версия для Windows XP/98/NT/2000 работает более-менее ожидаемым образом. Однако другие версии могут работать несколько иначе. Большинство несовпадений возникают, когда вы полагаетесь на вытесняющую многозадачность вместо совместного использования времени процессора. Наиболее безопасный способ получить предсказуемое межплатформенное поведение Java — это использовать потоки, которые принудительно осуществляют управление центральным процессором.

В следующем примере демонстрируются два потока с разными приоритетами, которые выполняются на платформе без вытесняющей многозадачности иначе, чем на платформе с упомянутой многозадачностью. Один поток получает приоритет на два уровня выше нормального, как определено Thread.NORM_PRIORITY, а другой — на два уровня ниже нормального. Потоки стартуют и готовы к выполнению в течение 10 секунд. Каждый поток выполняет цикл, подсчитывающий количество итераций. Через 10 секунд главный поток останавливает оба потока. Затем количество итераций цикла, которое успел выполнить каждый поток, отображается.

// Демонстрация приоритетов потоков.
class clicker implements Runnable {
long click = 0;
Thread t;
private volatile boolean running = true;
public clicker(int р) {
t = new Thread(this);
t.setPriority(p);
}
public void run() {
while (running) {
click++;
}
}
public void stop() {
running = false;
}
public void start () {
t. start () ;
}
}
class HiLoPri {
public static void main(String args[]) {
Thread.currentThread();
setPriority(Thread.MAX_PRIORITY);
clicker hi = new clicker(Thread.NORM_PRIORITY + 2);
clicker lo = new clicker(Thread.NORM_PRIORITY - 2) ;
lo.start () ;
hi.start () ;
try {
Thread.sleep(10000);
}
catch (InterruptedException e) {
System.out.println("Главный поток прерван.");
}
lo.stop() ;
hi.stop();
// Ожидание 10 секунд до прерывания.
try {
hi.t.join ();
lo.t.join();
}
catch (InterruptedException e) {
System.out.println("Перехвачено исключение InterruptedException");
}
System.out.println("Низкоприоритетный поток: " + lo.click);
System.out.println("Высокоприоритетный поток: " + hi.click);
}
}

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

Низкоприоритетный поток: 4408112
Высокоприоритетный поток: 589626904

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

Еще одно замечание относительно предыдущей программы. Обратите внимание, что переменной running предшествует слово volatile. Хотя volatile более подробно объясняется в главе 13, оно используется здесь, чтобы гарантировать, что значение running будет проверяться на каждом шаге итераций цикла:

while(running) {
click++;
}

Без указания volatile Java имеет возможность оптимизировать цикл таким образом, что будет создана локальная копия running. Применение volatile предотвращает эту оптимизацию, сообщая Java, что running может изменяться неявным для кода образом.