Главная » Java » Планирование потоков

0

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

результат будет выглядеть следующим образом: Did

Did

DidNot

DidNot

Если же каждый поток передает управление после очередного println, то другие потоки также получат возможность работать. Если присвоить doYield значение true:

Babble true 2 Did DidNot

то остальные потоки также смогут выполняться между очередными выводами и, в свою очередь, будут уступать управление, что приведет к следующему:

Did DidNot Did DidNot

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

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

По теме:

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