LXF87-88:Java
м |
(→Реализация потока через Runnable) |
||
Строка 48: | Строка 48: | ||
=== Реализация потока через Runnable === | === Реализация потока через Runnable === | ||
+ | Давайте теперь рассмотрим пример работы с потоками через интерфейс Runnable. Если, допустим, класс SameRunnable реализует интерфейс Runnable, то запустить поток на основе этого класса на выполнение можно следующим образом: | ||
+ | <source lang = "java"> | ||
+ | Runnable run = new SameRunnable(); | ||
+ | Thread thread = new Thread(run); | ||
+ | thread.start(); | ||
+ | </source> | ||
+ | В следующем примере в методе main() класса ConsoleToThreadTwo создается массив threadArray, состоящий из объектов-потоков. При | ||
+ | этом используется конструктор класса Thread, принимающий два параметра: ссылку на объект, реализующий интерфейс Runnable и | ||
+ | имя потока: | ||
+ | <source lang = "java"> | ||
+ | Thread thread = new Thread(Runnable, ThreadName); | ||
+ | </source> | ||
+ | Метод getName() объекта, реализующего поток, возвращает указанное при создании имя. Обратите внимание, что в нем используется | ||
+ | статический метод Thread.currentThread(), возвращающий ссылку на объект Thread, соответствующий выполняющемуся в текущий момент | ||
+ | потоку: | ||
+ | <source lang = "java"> | ||
+ | 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"); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | Проследив за выводом этой программы, можно заметить, что процессорное время распределяется между потоками практически равномерно, однако порядок их выполнения во многом случаен. | ||
+ | |||
=== Приоритеты потоков === | === Приоритеты потоков === | ||
=== Потоки-демоны === | === Потоки-демоны === |
Версия 13:15, 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"); } } }
Проследив за выводом этой программы, можно заметить, что процессорное время распределяется между потоками практически равномерно, однако порядок их выполнения во многом случаен.
Приоритеты потоков
Потоки-демоны
Где искать потоки?
Управление потоками
Мониторы и синхронизация
Взаимные блокировки
Литература
- 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