Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Секреты программирования для Internet на Java

.pdf
Скачиваний:
181
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

Создание иерархии исключений совершенно отличается от создания обычной объектноориентированной иерархии. Подклассы Exception выполняют мало полезной работы - только передают информацию о сбое в программе. Большинство исключений, определенных в API, не определяют никаких дополнительных методов или конструкторов, кроме принадлежащих классу

Throwable.

Самое важное, что касается подклассов Exception, - это их имена. Глядя на операторы catch, программист должен понимать, о какой именно ошибке идет речь. Пример удачного имени исключения - MalformedURLException ("неправильно сформированный URL"). Для того чтобы понять, в каком случае оно генерируется, нет нужды заглядывать в документацию.

Кроме того, исключения должны быть правильно сгруппированы. Вы, вероятно, помните, как мы запускали javadoc в главе 2. Поскольку исключения - всего лишь классы, javadoc можно запускать и с ними. Ваш подкласс класса Exception будет иметь собственную Web-страницу, на которой разместится ссылка на Web-страницу класса Exception, и те, кто будет пользоваться вашим кодом в дальнейшем, получат в руки исчерпывающее гипертекстовое руководствопутеводитель по пользовательским исключениям.

Теперь, построив иерархию исключений, мы можем приступить к разработке класса Convert. Полный текст класса Convert находится на приложенном к книге диске CD-ROM. Здесь мы приведем лишь методы, преобразующие в тип byte, а также еще один метод, используемый методом toByte(String S).

Пример 10-1b. Класс Convert. public class Convert {

public static byte toByte(short s) throws ShortMagnitudeLossException { byte b=(byte)s;

if (b==s) return b;

else throw(new ShortMagnitudeLossException(s));}

public static byte toByte(int i) throws IntMagnitudeLossException{ byte b=(byte)i;

if(i==b) return b;

else throw (new IntMagnitudeLossException(i));}

public static byte toByte(long l) throws LongMagnitudeLossException{ byte b=(byte)l;

if(l==b) return b;

else throw (new LongMagnitudeLossException(l));} public static byte toByte(String S) throws

StringFormatException, StringToByteException { try {

double d=toDouble(S); byte b=(byte)d;

if (b==d) return b;

else throw (new StringToByteException(d));} catch (StringFormatException e) {

throw (e);}

}

public static byte toByte(float f) throws MagnitudeLossException, FloatPrecisionLossException {

if(f>127 || f< -128)

throw (new MagnitudeLossException(f)); byte b=(byte)f;

if (b==f) return b;

else throw (new FloatPrecisionLossException(f));}

public static byte toByte(double d) throws MagnitudeLossException, DoublePrecisionLossException {

if(d>127 || d< -128)

throw (new MagnitudeLossException(d)); byte b=(byte)d;

if (b==d) return b;

else throw (new DoublePrecisionLossException(d));} //*** toDouble

public static double toDouble(String S) throws StringFormatException { S=S.trim();

try {

double d=Double.valueOf(S).doubleValue(); return d;}

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

catch (NumberFormatException e) {

throw (new StringFormatException(S));}

}

Информация об объектах при выполнении программы

Поскольку Java - язык динамический, мы можем получать информацию об объектах "на лету". Для этого используется класс Class - java.lang.Class (рис. 10-7). Этот класс необходим для того, чтобы можно было выяснить, что за тип у объекта, с которым мы работаем, какие интерфейсы в нем применены и ряд других характеристик.

Рис. 10.7.

Он редко применяется в небольших системах, когда мы точно знаем, экземпляры какого типа там попадаются. В больших и сложных системах мы можем использовать этот класс для того, чтобы разбирать, с каким экземпляром имеем дело. В табл. 10-3 перечислены методы, определенные в классе Class. Возвращая объект Class, они однозначно описывают определенный класс или интерфейс Java.

 

Таблица 10-3. Методы класса Class

Метод

Назначение и генерируемые исключения

static Class forName

Получив className,, данный метод возвращает экземпляр Class,,

(String className)

свойственный данному классу. Генерируемое исключение:

 

ClassNotFoundException.

String getName()

Возвращает имя данного класса,, связанное с данным экземпляром. Не

 

генерирует исключений.

String toString()

См. выше. Кроме того,, добавляется интерфейс,, если это - интерфейс,,

 

или класс,, если это - класс. Не генерирует исключений.

boolean isInterface()

Возвращает true,, если объект является интерфейсом. Не генерирует

 

исключений.

Class[] getInterfaces() Возвращает экземпляры Class,, соответствующие интерфейсам,, которыми владеет соответствующий класс или массив нулевой длины,, если у класса нет ни одного интерфейса. Не генерирует исключений.

Class getSuperclass()

Возвращает экземпляр Class суперкласса. Не генерирует исключений.

Object newInstance()

Создает новый экземпляр соответствующего класса. Генерирует

 

исключения: InstantiationException,, IllegalAccessException.

ClassLoader

Возвращает загрузчик класса. Не генерирует исключений.

getClassLoader()

 

www.books-shop.com

Метод newInstance используется, если нужно создавать экземпляры класса "на лету". Экземпляр создается следующим образом (предполагается, что S - имя существующего подкласса

Path):

try {

Path p = Class.forName(S).newInstance();

}

catch (InstantiationException e) {

System.out.println(S+" is not a valid subclass of "+Path);

}

catch (IllegalAccessException e) {

System.out.println(S+" is not allowed to be accessed");

}

catch (ClassNotFoundException e) { System.out.prinln(S+" wasn't found");

}

Что дальше?

Мы дали краткое введение в принципы построения программ и постарались показать возможности языка Java, связанные с написанием повторно используемого кода и объектной ориентации. Для того чтобы сделать программирование на Java еще мощнее, мы показали способы повышения устойчивости кода. В следующей главе будут рассмотрены способы построения многопоточных приложений.

Глава 11

www.books-shop.com

Многопотоковость

Создание потоков при помощи класса Thread Создание потоков при помощи интерфейса Runnable Управление потоками

Планирование потоков Группирование потоков Синхронизация потоков Переменные volatile

Мы уже кратко говорили об использовании потоков в языке Java. Java обрабатывает потоки очень изящным способом: это первый популярный язык, который включает потоки в спецификацию языка. Java, как мощный сетевой язык, должен был с самого начала уметь выполнять многопотоковые операции. Включение потоков делает Java уникальным языком разработки программ для Интернет.

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

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

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

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

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

СОВЕТ Фрагменты кода, приводимые в качестве примеров в этой главе, помещены на диск CDROM, прилагаемый к книге. Этим диском могут пользоваться те из читателей, кто работает с Windows 95/NT или Macintosh; пользователи UNIX должны обращаться к Web-странице Online Companion, на которой собраны сопроводительные материалы к этой книге (адрес http://www.vmedia.com/java.html).

Создание потоков при помощи класса Thread

Создание нового потока в Java - простейшая операция. Все, что вы должны сделать, - это расширить класс java.lang.Thread и заменить метод run. Каждый экземпляр этого нового класса будет выполнен как отдельный поток. Всего несколькими строками Java-кода вы можете создавать программы со многими потоками выполнения. Если вы когда-либо пытались сфабриковать фальшивые потоки, вы оцените простоту реализации потоков в Java.

В качестве первого упражнения давайте создадим поток outputThread для вывода некоторого текста. Этот поток отображает три числа и затем завершается.

Пример 11-1. Простой поток.

class outputThread extends Thread {

www.books-shop.com

outputThread(String name) { super(name);

}

public void run() {

for(int i=0; i < 3; i++) { System.out.println(getName()); Thread.yield();

}

}

}

class runThreads {

public static void main(String argv[]) {

outputThread t1 = new outputThread("Thread 1"); outputThread t2 = new outputThread("Thread 2"); t1.start();

t2.start();

}

}

На выходе код генерирует следующее:

Thread I

Thread 2

Thread I

Thread 2

Thread I

Thread 2

Обратите внимание, что в этой программе мы создаем два потока с помощью двух экземпляров класса outputThread. Затем мы вызываем метод start для каждого потока. Этот метод создает новый поток и затем вызывает наш замененный метод run. Вы уже создали программу с несколькими потоками! И это вовсе не так уж страшно или сложно - фактически в этой книге вы уже использовали потоки.

Помните апплеты для нашей Web-страницы? Мы расширили java.applet.Applet. Правда, это только один поток. Но каждый апплет на странице сделан при помощи одного или нескольких потоков; кроме того, оболочка времени выполнения Java создает некоторое количество потоков для себя. Вы уже знакомы с потоком сборки мусора, который освобождает неиспользуемую память. Как вы можете видеть, потоки - неотъемлемая часть Java.

Различия вывода

Некоторые читатели могут быть удивлены, что вывод на их компьютере несколько отличается от приведенного выше примера. Если вы видите весь вывод первого потока, а затем вывод второго потока, у вас многозадачная система, которая не выполняет квантование времени. Мы обсудим это в разделе "Планирование потоков" ниже в этой главе.

Создание потоков при помощи интерфейса Runnable

Что если бы мы не захотели расширять класс Thread в примере, показанном выше? Возможно, у нас уже есть класс, функциональные возможности которого нас вполне устраивают, и мы только хотим, чтобы он выполнялся как отдельный поток. Ответ прост: используйте интерфейс Runnable. Интерфейсы Java мы обсуждали в главе 3, "Объектная ориентация в Java". Интерфейсы дают хороший способ определить набор стандартных функциональных возможностей для выполняемого класса. Интерфейс Runnable реализует один метод - run, который очень похож на работу класса Thread.

Представьте, что у нас есть класс outputClass, который мы хотим переделать в поток. Все, что мы должны сделать, - это реализовать интерфейс Runnable, создавая разделяемый метод run.

Пример 11-2. Использование интерфейса Runnable. class outputClass implements Runnable {

String name; outputClass(String s) {

name = s;

www.books-shop.com

}

public void run() {

for(int i=0; i < 3; i++) { System.out.println(name); Thread.yield();

}

}

}

class runThreads {

public static void main(String argv[]) {

outputClass out1 = new outputClass("Thread 1"); outputClass out2 = new outputClass("Thread 2"); Thread t1 = new Thread(out1);

Thread t2 = new Thread(out2); t1.start();

t2.start();

}

}

Этот пример по функциям эквивалентен примеру 11-1, но выполнен по-другому. Здесь мы создаем два экземпляра класса outputClass. Это могли бы быть любые классы, но они должны реализовывать интерфейс Runnable. Затем мы создаем два новых потока и передаем их созданным экземплярам outputClass. После этого мы начинаем выполнение потоков как обычно.

Различие между двумя примерами - в обращении конструктора к классу Thread. В первом примере мы вызвали конструктор Thread(String), во втором - Thread(Runnable). Мы могли бы также вызвать конструктор Thread(Runnable, String). Класс Thread имеет следующие конструкторы:

Thread()

Thread (String)

Thread(Runnable)

Thread (String, Runnable)

Thread (ThreadGroup, String)

Thread (ThreadGroup, Runnable)

Thread(ThreadGroup, Runnable, String)

Мы обсудим ThreadGroup немного позже. Сейчас давайте исследуем, как управлять потоками. Создание потоков - только часть дела; главное - научиться управлять ими после того, как они порождены.

Управление потоками

До сих пор мы выполняли потоки, которые заканчиваются сами собой. Они выполняют задачу и завершаются. А как вы остановите поток, когда Java-программа заканчивает свою работу? Это важный вопрос, и нам понадобятся некоторые общие знания, чтобы на него ответить.

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

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

Все запущенные на текущее время потоки находятся в состоянии выполнения. Процессор разделяет время между потоками (как именно Java выделяет, распределяет время процессора, мы обсудим ниже).

В этом состоянии каждый поток доступен для выполнения, но в любой момент времени на процессоре системы может действительно выполняться только одна программа.

Потоки попадают в состояние ожидания, если их выполнение было прервано. Поток может

www.books-shop.com

быть прерван несколькими способами. Он может быть приостановлен для ожидания некоторых ресурсов системы или по требованию о приостановке. Из этого состояния поток может быть возвращен к состоянию выполнения или переведен в выполненное состояние методом stop. В табл. 11-1 приведены методы, управляющие выполнением потоков.

 

Таблица 11-1. Методы управления потоками

 

Метод

Описание

Исходное состояние Новое состояние

start()

Начинает выполнение потока

Создание

Выполнение

stop()

Заканчивает выполнение потока

Создание, выполнение Завершение

sleep(long)

Пауза на некоторое число миллисекунд Выполнение

Ожидание

sleep(long,int) Пауза на некоторое число наносекунд

Выполнение

Ожидание

suspend()

Приостанавливает выполнение

Выполнение

Ожидание

resume()

Продолжает выполнение

Ожидание

Выполнение

yield()

Явно уступает управление

Выполнение

Выполнение

Методы, приведенные в табл. 11-1, не всегда доступны; большинство из них работает, когда поток выполняется. Если вы используете какой-то метод в несоответствующем состоянии - например, если вы попробуете приостановить завершенный поток, - будет сгенерировано исключение IllegalThreadStateException. Во время разработки вы должны знать, в каком состоянии находится поток. Если вам нужна программа для определения состояний потоков, используйте метод isAlive: результат true означает, что поток выполняется или ожидает. Никакого способа определить разницу между выполнением и ожиданием нет.

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

Планирование потоков

Порядок, в котором потоки будут выполняться, и количество времени, которое они получат от процессора, - главные вопросы для разработчика. Каждый поток должен разделять процессорное время с другими. Мы не хотим, чтобы один поток монополизировал целую систему. Планирование потоков близко связано с двумя понятиями: выгрузка и квантование времени. Давайте исследуем каждое понятие подробнее.

Система, которая имеет дело со множеством выполняющихся потоков, может быть или приоритетная, или неприоритетная. Приоритетные системы гарантируют, что в любое время будет выполняться поток с самым высоким приоритетом. Всем потокам в системе приписывается приоритет. Переменная класса Thread.NORM_Priority содержит значение приоритета потока по умолчанию. В классе Thread есть методы setPriority и getPriority для установки и определения приоритета. Используя метод setPriority, можно изменять важность потока для виртуальной машины Java. Этот метод в качестве аргумента получает некоторое целое число в диапазоне допустимых значений, заданном двумя переменными класса Thread.MIN_PRIORITY и Thread.MAX_PRIORITY.

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

Пример 11-3. Планирование.

class outputThread extends Thread { outputThread(String name) {

super(name);

}

public void run() {

for(int i=0; i < 3; i++) { System.out.println(getName());

}

}

}

class runThreads {

public static void main(String argv[]) {

www.books-shop.com

outputThread t1 = new outputThread("Thread 1"); outputThread t2 = new outputThread("Thread 2"); outputThread t3 = new outputThread("Thread 3"); t1.start();

t2.start();

t3.start();

}

}

Создавая этот код, мы вообще-то ожидаем, что каждый поток того же самого приоритета должен выполняться в течение некоторого времени и затем уступать управление другому потоку. Как можно увидеть из вывода этого примера, выполненного нашей программой на машине с Windows 95, мы получаем то, что и ожидали:

Thread 1

Thread 2

Thread 3

Thread 1

Thread 2

Thread 3

Thread 1

Thread 2

Thread 3

Но посмотрим, что мы получим, выполнив тот же самый код на рабочей станции Sun:

Thread I

Thread I

Thread I

Thread 2

Thread 2

Thread 2

Thread 3

Thread 3

Thread 3

Результаты совершенно различны! Что случилось с платформонезависимостью? Добро пожаловать в теневой мир многопотоковости. Не все машины созданы одинаково, и операционная система воздействует на порядок выполнения потоков. Различие заключается в концепции, называемой квантованием времени.

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

Давайте поближе взглянем на то, что же случилось, когда мы запустили нашу программу на машине Sun. Первый поток выполнялся до завершения, потом так делал поток 2 и, наконец, поток 3. Но если первый поток никогда не закончится, потоки 2 и 3 никогда не будут выполняться. Конечно, это вызывает некоторые проблемы. Поток с самым высоким приоритетом будет выполняться всегда, в то время как потоки более низкого приоритета, в зависимости от операционной системы, возможно, выполниться не смогут. Если нам нужна некоторая упорядоченость или даже просто последовательное выполнение потоков, мы должны будем проделать некоторую работу.

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

Пример 11-4. Метод yield.

class outputThread extends Thread { outputThread(String name) {

super(name);

www.books-shop.com

}

public void run() {

for(int i=0; i < 3; i++) { System.out.println(getName()); Thread.yield();

}

}

}

class runThreads {

public static void main(String argv[]) {

outputThread t1 = new outputThread("Thread 1"); outputThread t2 = new outputThread("Thread 2"); outputThread t3 = new outputThread("Thread 3"); t1.start();

t2.start();

t3.start();

}

}

Этот вариант потребовал только одного изменения. Мы добавили команду yield внутрь основного цикла потока. Хотя это дает нам то, что мы хотим, у такого решения есть несколько недостатков. Метод yield затрачивает ресурсы системы. В случае одного потока использование yield - это пустая трата времени. Не забудьте также, что некоторые системы уже обеспечивают для нас разделение времени. Другой недостаток состоит в том, что мы должны явно уступить управление, - но как узнать, где и как часто это делать?

Так как некоторые системы выполняют квантование времени, а другие этого не делают, мы могли бы разработать простой тест, чтобы определить способности системы. Следующий код определяет, действительно ли система обеспечивает квантование времени.

Пример 11-5a. Тестирующий поток testThread. class testThread extends Thread {

protected int val=0; public void run() { while(true) { val++;

}

}

public int getVal() { return val;

}

}

Этот первый класс - только поток теста. Он просто увеличивает некоторую переменную. В системе с квантованием времени все текущие потоки должны иметь равные значения. Системы без квантования времени, вероятно, закончат выполнение с одним потоком, получающим значительно большее количество времени, чем другие. Следующий код определяет, выполняет ли система квантование времени.

Пример 11-5b. Определение наличия квантования времени. class isFair extends Thread {

boolean fair=false; boolean determined=false; public boolean isFair() {

if (determined) return fair; start();

while(!determined) {

// ожидание, пока значение равно determined try {

sleep(1500);

}

catch (InterruptedException e) {

}

}

www.books-shop.com

return fair;

}

public void run() {

testThread t1 = new testThread(); testThread t2 = new testThread(); setPriority(MAX_PRIORITY); t1.start();

t2.start(); try {

sleep(500);

}

catch (InterruptedException e) {

}

t1.stop();

t2.stop();

if (t1.getVal() > 2 * t2.getVal()) { fair = false;

}

else {

fair = true;

}

determined = true;

}

}

Класс isFair - основная часть этого примера. В нем есть метод isFair, который может вызываться, чтобы определить, имеет ли система возможности квантования времени. Несколько миллисекунд он управляет двумя потоками testThreads. Затем он останавливает потоки и проверяет результат. Если один поток получил времени вдвое больше, чем другой, мы назовем это разделение несправедливым. Вообще, этот пример действует по принципу "все или ничего"; один поток, обычно thread1, получает все процессорное время. Это будет несправедливое разделение времени.

Пример 11-5c. Тест справедливости разделения времени. class test {

public static void main(String argv[]) { isFair tThread = new isFair(); if (tThread.isFair()) {

System.out.println("System has time slicing");

}

else {

System.out.println("System does not time slice");

}

}

}

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

Поместить метод yeld в своем коде нетрудно, но можно просто забыть это сделать. Есть другие способы решения той же задачи. Поток с самым высоким приоритетом в системе всегда выполняется, если не находится в режиме ожидания. Мы можем использовать это, чтобы подражать квантованию времени. В примере 11-6 создается поток slicerThread, чей приоритет установлен в MAX_PRIORITY. Его задача состоит только в том, чтобы ничего не делать. Когда он бездействует, ему больше не требуется планирование. Это означает, что наши потоки с нормальным приоритетом получат возможность выполниться. Каждый раз, когда slicerThread получает управление и засыпает, планировщик выбирает новый поток для выполнения. Он выбирает их циклически, так что каждый нормальный поток получит возможность выполниться, как показано в следующем примере.

Пример 11-6. Квантование времени.

class outputThread extends Thread {

www.books-shop.com