Программирование на Java
Шрифт:
Такие обслуживающие потоки называют демонами ( daemon ), это свойство можно установить любому потоку. В итоге приложение выполняется до тех пор, пока есть хотя бы один поток не- демон.
Рассмотрим, как потоки реализованы в Java.
Базовые классы для работы с потоками
Класс Thread
Поток выполнения в Java представляется экземпляром класса Thread. Для того, чтобы написать свой поток исполнения, необходимо наследоваться от этого класса и переопределить метод run. Например,
public class MyThread extends Thread {
public void run {
//
long sum=0;
for (int i=0; i<1000; i++) {
sum+=i;
}
System.out.println(sum);
}
}
Метод run содержит действия, которые должны выполняться в новом потоке исполнения. Чтобы запустить его, необходимо создать экземпляр класса-наследника и вызвать унаследованный метод start, который сообщает виртуальной машине, что требуется запустить новый поток исполнения и начать выполнять в нем метод run.
MyThread t = new MyThread;
t.start;
В результате чего на консоли появится результат:
499500
Когда метод run завершен (в частности, встретилось выражение return ), поток выполнения останавливается. Однако ничто не препятствует записи бесконечного цикла в этом методе. В результате поток не прервет своего исполнения и будет остановлен только при завершении работы всего приложения.
Интерфейс Runnable
Описанный подход имеет один недостаток. Поскольку в Java множественное наследование отсутствует, требование наследоваться от Thread может привести к конфликту. Если еще раз посмотреть на приведенный выше пример, станет понятно, что наследование производилось только с целью переопределения метода run. Поэтому предлагается более простой способ создать свой поток исполнения. Достаточно реализовать интерфейс Runnable, в котором объявлен только один метод – уже знакомый void run. Запишем пример, приведенный выше, с помощью этого интерфейса:
public class MyRunnable implements Runnable {
public void run {
// некоторое долгое действие, вычисление
long sum=0;
for (int i=0; i<1000; i++) {
sum+=i;
}
System.out.println(sum);
}
}
Также незначительно меняется процедура запуска потока:
Runnable r = new MyRunnable;
Thread t = new Thread(r);
t.start;
Если раньше объект, представляющий сам поток выполнения, и объект с методом run, реализующим необходимую функциональность, были объединены в одном экземпляре класса MyThread, то теперь они разделены. Какой из двух подходов удобней, решается в каждом конкретном случае.
Подчеркнем, что Runnable не является полной заменой классу Thread, поскольку создание и запуск самого потока исполнения возможно только через метод Thread.start.
Работа с приоритетами
Рассмотрим,
MIN_PRIORITY
MAX_PRIORITY
NORM_PRIORITY
Из названия понятно, что их значения описывают минимальное, максимальное и нормальное (по умолчанию) значения приоритета.
Рассмотрим следующий пример:
public class ThreadTest implements Runnable {
public void run {
double calc;
for (int i=0; i<50000; i++) {
calc=Math.sin(i*i);
if (i%10000==0) {
System.out.println(getName+ " counts " + i/10000);
}
}
}
public String getName {
return Thread.currentThread.getName;
}
public static void main(String s[]) {
// Подготовка потоков Thread t[] = new Thread[3];
for (int i=0; i<t.length; i++) {
t[i]=new Thread(new ThreadTest, "Thread "+i);
}
// Запуск потоков
for (int i=0; i<t.length; i++) {
t[i].start;
System.out.println(t[i].getName+ " started");
}
}
}
В примере используется несколько новых методов класса Thread:
* getName
Обратите внимание, что конструктору класса Thread передается два параметра. К реализации Runnable добавляется строка. Это имя потока, которое используется только для упрощения его идентификации. Имена нескольких потоков могут совпадать. Если его не задать, то Java генерирует простую строку вида "Thread-" и номер потока (вычисляется простым счетчиком). Именно это имя возвращается методом getName. Его можно сменить с помощью метода setName.
* currentThread
Этот статический метод позволяет в любом месте кода получить ссылку на объект класса Thread, представляющий текущий поток исполнения.
Результат работы такой программы будет иметь следующий вид:
Thread 0 started
Thread 1 started
Thread 2 started
Thread 0 counts 0
Thread 1 counts 0
Thread 2 counts 0
Thread 0 counts 1
Thread 1 counts 1
Thread 2 counts 1
Thread 0 counts 2
Thread 2 counts 2
Thread 1 counts 2
Thread 2 counts 3
Thread 0 counts 3
Thread 1 counts 3
Thread 2 counts 4
Thread 0 counts 4
Thread 1 counts 4
Мы видим, что все три потока были запущены один за другим и начали проводить вычисления. Видно также, что потоки исполняются без определенного порядка, случайным образом. Тем не менее, в среднем они движутся с одной скоростью, никто не отстает и не догоняет.
Введем в программу работу с приоритетами, расставим разные значения для разных потоков и посмотрим, как это скажется на выполнении. Изменяется только метод main.