Программирование на Java
Шрифт:
Рассмотрим следующий пример:
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.
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);
t[i].setPriority(Thread.MIN_PRIORITY +
(Thread.MAX_PRIORITY -
Thread.MIN_PRIORITY)/t.length*i);
}
// Запуск потоков
for (int i=0; i<t.length; i++) {
t[i].start;
System.out.println(t[i].getName+
" started");
}
}
Формула вычисления приоритетов позволяет равномерно распределить все допустимые значения для всех запускаемых потоков. На самом деле, константа минимального приоритета имеет значение 1, максимального 10, нормального 5. Так что в простых программах можно явно пользоваться этими величинами и указывать в качестве, например, пониженного приоритета значение 3.
Результатом работы будет:
Thread 0 started
Thread 1 started
Thread 2 started
Thread 2 counts 0
Thread 2 counts 1
Thread 2 counts 2
Thread 2 counts 3
Thread 2 counts 4
Thread 0 counts 0
Thread 1 counts 0
Thread 1 counts 1
Thread 1 counts 2
Thread 1 counts 3
Thread 1 counts 4
Thread 0 counts 1
Thread 0 counts 2
Thread 0 counts 3
Thread 0 counts 4
Потоки,
как и раньше, стартуют последовательно. Но затем мы видим, что чем выше приоритет, тем быстрее отрабатывает поток. Тем не менее, весьма показательно, что поток с минимальным приоритетом ( Thread 0 ) все же получил возможность выполнить одно действие раньше, чем отработал поток с более высоким приоритетом ( Thread 1 ). Это говорит о том, что приоритеты не делают систему однопоточной, выполняющей единовременно лишь один поток с наивысшим приоритетом. Напротив, приоритеты позволяют одновременно работать над несколькими задачами с учетом их важности.Если увеличить параметры метода (выполнять 500000 вычислений, а не 50000, и выводить сообщение каждое 1000-е вычисление, а не 10000-е), то можно будет наглядно увидеть, что все три потока имеют возможность выполнять свои действия одновременно, просто более высокий приоритет позволяет выполнять их чаще.
Демон-потоки
Демон -потоки позволяют описывать фоновые процессы, которые нужны только для обслуживания основных потоков выполнения и не могут существовать без них. Для работы с этим свойством существуют методы setDaemon и isDaemon.
Рассмотрим следующий пример:
public class ThreadTest implements Runnable {
// Отдельная группа, в которой будут
// находиться все потоки ThreadTest
public final static ThreadGroup GROUP = new ThreadGroup("Daemon demo");
// Стартовое значение, указывается при создании объекта
private int start;
public ThreadTest(int s) {
start = (s%2==0)? s: s+1;
new Thread(GROUP, this, "Thread "+ start).start;
}
public void run {
// Начинаем обратный отсчет
for (int i=start; i>0; i--) {
try {
Thread.sleep(300);
}
catch (InterruptedException e) {
}
// По достижении середины порождаем
// новый поток с половинным начальным
// значением
if (start>2 && i==start/2)
{
new ThreadTest(i);
}
}
}
public static void main(String s[]) {
new ThreadTest(16);
new DaemonDemo;
}
}
public class DaemonDemo extends Thread {
public DaemonDemo {
super("Daemon demo thread");
setDaemon(true);
start;
}
public void run {
Thread threads[]=new Thread[10]; while (true) {
// Получаем набор всех потоков из
// тестовой группы
int count=ThreadTest.GROUP.activeCount;
if (threads.length<count) threads = new Thread[count+10]; count=ThreadTest.GROUP.enumerate(threads);
// Распечатываем имя каждого потока