LXF87-88:Java
(→Реализация потока через Runnable) |
(→Приоритеты потоков) |
||
Строка 93: | Строка 93: | ||
=== Приоритеты потоков === | === Приоритеты потоков === | ||
+ | Для управления величиной процессорного времени, выделяемого потоку, можно воспользоваться приоритетами. Установка приоритетов происходит с помощью метода Thread.setPriority(), узнать текущий приоритет позволяет метод getPriority(). В классе Thread определены | ||
+ | три константы: | ||
+ | <source lang = "java"> | ||
+ | MIN_PRIORITY = 1 | ||
+ | NORM_PRIORITY = 5 | ||
+ | MAX_PRIORITY = 10 | ||
+ | </source> | ||
+ | Важно понимать, что значение приоритета потока предназначено для Java-машины и не соответствует реальным приоритетам потоков | ||
+ | в операционной системе. | ||
+ | |||
+ | Давайте немного изменим код метода main() класса ConsoleToThreadTwo: | ||
+ | <source lang = "java"> | ||
+ | public static void main(String[] args) { | ||
+ | Thread[] threadArray = new Thread[3]; | ||
+ | int pr = 0; | ||
+ | for (int i=0; i<threadArray.length; i++){ | ||
+ | threadArray[i] = new Thread(new SecondThread(), "Thread " + i); | ||
+ | if (pr == 10) | ||
+ | pr = 0; | ||
+ | threadArray[i].setPriority(Thread.MIN_PRIORITY + pr); | ||
+ | pr++; | ||
+ | } | ||
+ | for (int i=0; i<threadArray.length; i++){ | ||
+ | threadArray[i].start(); | ||
+ | System.out.println(threadArray[i].getName() + " started"); | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | Анализ результатов работы класса показывает, что потоки, получившие более высокий приоритет, выполняются чаще. Также, благодаря | ||
+ | условию на значение переменной pr, setPriority() никогда не будет передан приоритет, превышающий 10 (MAX_PRIORITY). Если бы это про- | ||
+ | изошло, система выбросила бы исключение IllegalArgumentException. | ||
+ | |||
=== Потоки-демоны === | === Потоки-демоны === | ||
=== Где искать потоки? === | === Где искать потоки? === |
Версия 13:23, 17 марта 2008
|
|
|
Содержание |
Потоки в Java
ЧАСТЬ 4: Завершая курс молодого Java-бойца, Антон Черноусов научит вас управлять потоками... Жаль, что не денежными.
C каждым днем появляются все более мощные процессоры, многоядерная архитектура которых стала основной темой ушедшего года, поэтому двухядерный процессор в ноутбуке уже никого не удивляет. С одной стороны – это обстоятельство приближает возможности простого пользователя к возможностям «понастоящему» больших систем. С другой (и рекламные буклеты об этом обычно молчат) – для того, чтобы использовать весь потенциал современных компьютеров, приложение должно «уметь» просчитать задачу фактически на двух или более процессорах.
Создание эффективных алгоритмов для работы на многопроцессорных станциях – это большая и сложная работа. Несмотря на это, для любого программиста актуальна задача организации взаимодействия с медленными ресурсами (например, чтения, записи или копирования файлов, работы с принтером, сетью), так как немногие пользователи смирятся с тем, что их любимая программа «замирает» в момент выполнения какой-либо операции.
Во избежание описанных проблем программа должна использовать потоки или процессы. Под процессом понимается заявка на потребле- ние всех видов ресурсов системы, кроме одного – процессорного времени, или иначе говоря, процесс – это запущенная на выполнение программа (такое определение дается в [1]). Поток рассматривается как самостоятельная активность внутри процесса, хотя существуют другие трактовки этого понятия, которые зависят от используемой операционной системы (см., например, [2]). Поток получил свое название по аналогии с потоком команд, поступающих в процессор; при выполнении потоки делят адресное пространство и выделенную память внутри одного процесса. Процессорное время распределяется между различными потоками операционной системой, точнее, одним из компонентов ее ядра – планировщиком. Более полно с понятием процессов и потоков и механизмов работы с ними с точки зрения операционной системы вы можете ознакомиться в книге [3].
Давайте завершим наш экскурс в теорию и окунемся в реальность Java. Под процессом здесь принято понимать всеобъемлющий контекст выполнения, обеспечивающий высокий уровень изоляции охватываемых им данных от внешнего мира, а под потоком – более «легковесный» активный агент; в контексте одного процесса может функционировать целое множество потоков [4]. Планирование потоков в Java обеспечивается внутренними механизмами JVM.
Поток, он же thread
В Java существует два способа работы с потоками: первый заключается в реализации интерфейса Runnable, второй связан с наследованием класса Thread, который уже реализует данный интерфейс. В обоих случаях класс должен предоставлять реализацию метода run(). Ниже приведен пример класса, реализующего поток через наследование класса Thread:
public class FirstThread extends Thread { public void run(){ for (int i = 1 ; i < 30; i++) System.out.println("It is in thread "+ i); } }
Собственно, метод run() и должен содержать некоторый набор инструкций (разумеется, на языке Java), которые вы хотите выполнить в отдельном потоке. Например, если вы реализуете функцию копирования файла (а пользователь, скажем, копирует ISO-образ объемом 600 Мб), желательно, чтобы эта операция выполнялась в отдельном потоке, запуск которого можно производить следующим образом:
public class ConsoleToThread { public static void main(String[] args) { FirstThread thread = new FirstThread(); thread.start(); for (int i = 1; i < 20; i++) { System.out.println("It is in main " + i); } try { thread.join(); } catch (InterruptedException ex) { System.out.println("Exception in stop thread"); } } }
При выполнении метода main() класса ConsoleToThread создается объект-поток FirstThread, который запускается на выполнение методом start() [заметьте – метод run() никогда не вызывается явно, – прим.ред.]. Метод join() используется в случае, когда необходимо «дождаться» завершения потока. Завершение работы потока происходит при выходе из метода run(), как явном (например, посредством return), так и неявном (если внутри метода возникло и не было обработано какое-то исключение).
Имейте в виду (это важно!): повторный запуск уже отработавшего потока приведет к исключению IllegalThreadStateException.
Реализация потока через Runnable
Давайте теперь рассмотрим пример работы с потоками через интерфейс Runnable. Если, допустим, класс SameRunnable реализует интерфейс Runnable, то запустить поток на основе этого класса на выполнение можно следующим образом:
Runnable run = new SameRunnable(); Thread thread = new Thread(run); thread.start();
В следующем примере в методе main() класса ConsoleToThreadTwo создается массив threadArray, состоящий из объектов-потоков. При этом используется конструктор класса Thread, принимающий два параметра: ссылку на объект, реализующий интерфейс Runnable и имя потока:
Thread thread = new Thread(Runnable, ThreadName);
Метод getName() объекта, реализующего поток, возвращает указанное при создании имя. Обратите внимание, что в нем используется статический метод Thread.currentThread(), возвращающий ссылку на объект Thread, соответствующий выполняющемуся в текущий момент потоку:
public class SecondThread implements Runnable { public String getName() { return Thread.currentThread().getName(); } public void run() { for (int i = 1; i < 100000; i++) { if ((i % 10000) == 0) { System.out.println(getName() + " counts " + i / 10000); } } } } public class ConsoleToThreadTwo { public static void main(String[] args) { Thread[] threadArray = new Thread[3]; for (int i=0; i<threadArray.length; i++){ threadArray[i] = new Thread(new SecondThread(), "Thread " + i); } for (int i=0; i<threadArray.length; i++){ threadArray[i].start(); System.out.println(threadArray[i].getName() + " started"); } } }
Проследив за выводом этой программы, можно заметить, что процессорное время распределяется между потоками практически равномерно, однако порядок их выполнения во многом случаен.
Приоритеты потоков
Для управления величиной процессорного времени, выделяемого потоку, можно воспользоваться приоритетами. Установка приоритетов происходит с помощью метода Thread.setPriority(), узнать текущий приоритет позволяет метод getPriority(). В классе Thread определены три константы:
MIN_PRIORITY = 1 NORM_PRIORITY = 5 MAX_PRIORITY = 10
Важно понимать, что значение приоритета потока предназначено для Java-машины и не соответствует реальным приоритетам потоков в операционной системе.
Давайте немного изменим код метода main() класса ConsoleToThreadTwo:
public static void main(String[] args) { Thread[] threadArray = new Thread[3]; int pr = 0; for (int i=0; i<threadArray.length; i++){ threadArray[i] = new Thread(new SecondThread(), "Thread " + i); if (pr == 10) pr = 0; threadArray[i].setPriority(Thread.MIN_PRIORITY + pr); pr++; } for (int i=0; i<threadArray.length; i++){ threadArray[i].start(); System.out.println(threadArray[i].getName() + " started"); } }
Анализ результатов работы класса показывает, что потоки, получившие более высокий приоритет, выполняются чаще. Также, благодаря условию на значение переменной pr, setPriority() никогда не будет передан приоритет, превышающий 10 (MAX_PRIORITY). Если бы это про- изошло, система выбросила бы исключение IllegalArgumentException.
Потоки-демоны
Где искать потоки?
Управление потоками
Мониторы и синхронизация
Взаимные блокировки
Литература
- 1. П. Кью «Использование UNIX», ISBN 5-8275-0019-4
- 2. В.Г. Олифер, Н.А. Олифер «Сетевые операционные системы», ISBN 5-272-00120-6
- 3. Д. Бэкон, Т. Харрис «Операционные системы», ISBN 5-94723-969-8
- 4. М. Фаулер «Архитектура корпоративных программных приложений», ISBN 5-8459-0579-6