//: concurrency/MainThread.java
public class MainThread {
public static void main(String[] args) { Liftoff launch = new LiftOffO; launch.run();
}
} /* Output-
#0(9). #0(8). #0(7), #0(6). #0(5). #0(4). #0(3). #0(2). #0(1). #0(Liftoff!).
*///:-
Класс, реализующий Runnable, должен содержать метод run(), но ничего особенного в этом методе нет — он не обладает никакими особыми потоковыми возможностями. Чтобы использовать многопоточное выполнение, необходимо явно связать задачу с потоком.
Класс Thread
Традиционный способ преобразования объекта Runnable в задачу заключается в передаче его конструктору Thread. Следующий пример показывает, как организовать выполнение LiftOff с использованием Thread:
//: concurrency/BasicThreads.java
// Простейший вариант использования класса Thread.
public class BasicThreads { public static void main(String[] args) { Thread t = new Thread (new LiftOffO); t.startO;
System.out.println("Waiting for LiftOff");
}
} /* Output: (90* match)
Waiting for LiftOff
#0(9). #0(8). #0(7). #0(6). #0(5). #0(4). #0(3). #0(2). #0(1). #0(Liftoff!).
*///:-
Конструктору Thread передается только объект Runnable. Метод start() выполняет необходимую инициализацию потока, после чего вызов метода run() интерфейса Runnable запускает задачу на выполнение в новом потоке.
Из выходных данных видно, что вызов start() быстро возвращает управление (сообщение «Waiting for LiftOff» появляется до завершения отсчета). В сущности, мы вызываем LiftOff.runQ, а этот метод еще не завершил свое выполнение;
но, поскольку LiftOff.run() выполняется в другом потоке, в потоке main() в это время можно выполнять другие операции. (Данная возможность не ограничивается потоком main() — любой поток может запустить другой поток.) Получается, что программа выполняет два метода сразу — main() и LiftOff. run().
В программе можно легко породить дополнительные потоки для выполнения дополнительных задач:
// concurrency/MoreBasicThreads java
// Добавление новых потоков
public class MoreBasicThreads {
public static void main(String[] args) { for(int i = 0, l < 5, i++)
new Thread(new LiftOffO) startO. System.out println("Waiting for LiftOff"),
#2(7), #3(7) #4(5), #0(4) #1(2), #2(2) #l(Liftoff!) *///:-#4(7), #0(6) #1(4), #2(4) #3(2), #4(2) #2(Liftoff!) #4(9), #0(8) #1(6), #2(6) #3(4), #4(4) #0(1), #1(1) #3(Liftoff!) #1(8), #2(8) #3(6), #4(6) #0(3), #1(3) #2(1), #3(1) #4(Liftoff!)
#3(8), #4(8). #0(7). #1(7).
#0(5), #1(5). #2(5). #3(5),
#2(3), #3(3). #4(3). #0(2).
#4(1), #0(Liftoff!),
} /* Output. (Пример) Waiting for LiftOff #0(9), #1(9), #2(9), #3(9)
Из выходных данных видно, что задачи выполняются одновременно друг с другом, с поочередной активизацией и выгрузкой потоков. Переключение осуществляется автоматически планировщиком потоков. Если на компьютере установлено несколько процессоров, планировщик потоков автоматически распределяет потоки между разными процессорами.
При разных запусках программы будут получены разные результаты, поскольку работа планировщика потоков недетерминирована. Более того, вы наверняка увидите значительные различия в результатах работы данной программы-примера, запуская ее на различных версиях пакета JDK. К примеру, предыдущие версии JVM не слишком часто выполняли квантование времени, соответственно, поток 1 мог первым закончить свой цикл, затем все свои итерации произвел бы поток 2, и т. д. Фактически то же самое получилось бы, если бы вызывалась процедура, выполняющая все циклы одновременно, за тем исключением, что запуск совокупности потоков требует больших издержек. Более поздние версии JDK обеспечивают более качественное квантование, и каждый поток регулярно получает свою долю внимания. Как правило, Sun не упоминает о подобных изменениях, так что рассчитывать на определенные «правила поведения» потоков не стоит. Лучше всего при написании кода с потоками занять максимально консервативную позицию.