Самый простой способ создать поток исполнения состоит в том, чтобы объявить класс, реализующий интерфейс Runnable. Этот интерфейс предоставляет абстракцию единицы исполняемого кода.
Поток исполнения можно создать из объекта любого класса, реализующего интерфейс Runnable. Для реализации интерфейса Runnable в классе должен быть объявлен единственный метод run():
1 |
public void run() |
В теле метода run() определяется код, который , собственно, и составляет новый поток исполнения. Однако в методе run() можно вызывать другие методы, использовать другие классы, объявлять переменные таким же образом, как и в главном
потоке исполнения.
Единственное отличие заключается в том, что в методе run() устанавливается точка входа в другой, параллельный поток исполнения в программе. Этот поток исполнения завершится, когда метод run() возвратит управление.
После создания класса, реализующего интерфейс Runnable, в этом классе следует получить экземпляр объекта типа Thread. Для этой цели в классе Thread определен ряд конструкторов.
Тот конструктор, который должен использоваться в данном случае, выглядит в общей форме следующим образом:
1 |
Thread(Runnable объект_потока, String имя_потока) |
В этом конструкторе параметр объект_потока обозначает экземпляр класса, реализующего интерфейс Runnable. Этим определяется место, где начинается выполнение потока.
Имя нового потока исполнения передается данному конструктору в качестве параметра имя_потока.
После того как новый поток исполнения будет создан, он не запускается до тех пор, пока не будет вызван метод start(), объявленный в классе Thread. По существу, в методе start() вызывается метод run().
Ниже показано, каким образом объявляется метод start().
1 |
void start() |
Рассмотрим следующий пример программы, демонстрирующий создание и пуск нового потока на выполнение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// Создать второй поток исполнения class NewThread implements Runnable { Thread t; NewThread() { // Создать новый, второй поток исполнения t = new Thread(this, "Демонстрационный поток"); System.out.println("Дочерний поток создан: " + t); t.start(); // Запустить поток исполнения } // Точка входа во второй поток исполнения public void run() { try { for(int i = 5; i > 0; i--) { System.out.println("Дочерний поток: " + i); Thread.sleep(500); } } catch (InterruptedException e) { System.out.println("Дочерний поток прерван."); } System.out.println("Дочерний поток завершен."); } } class ThreadDemo { public static void main(String args[]) { new NewThread(); // создать новый поток try { for(int i = 5; i > 0; i--) { System.out.println("Главный поток: " + i); Thread.sleep(1000); } } catch (InterruptedException e) { System.out.println("Главный поток прерван."); } System.out.println("Главный поток завершен."); } } |
Новый объект класса Thread создается в следующем операторе из конструктора NewThread():
1 |
t = new Thread(this, "Демонстрационный поток"); |
Передача ссылки this на текущий объект в первом аргументе данного конструктора означает следующее: в новом потоке исполнения для текущего объекта по ссылке this следует вызвать метод run(). Далее в приведенном выше примере программы вызывается метод start(), в результате чего поток исполнения запускается, начиная с метода run().
Это, в свою очередь, приводит к началу цикла for в дочернем потоке исполнения. После вызова метода start() конструктор NewThread() возвращает управление методу main().
Возобновляя свое исполнение, главный поток входит в свой цикл for. Далее потоки выполняются параллельно, совместно используя ресурсы процессора в одноядерной системе, вплоть до завершения своих циклов.
Ниже приведен результат, выводимый данной программой (заметьте, у вас он может оказаться иным в зависимости от конкретной исполняющей среды).
1 2 3 4 5 6 7 8 9 10 11 12 13 |
дочерний поток : Тhrеаd [ Демонстрационный поток, 5, mаin ] Главный поток : 5 Дочерний поток : 5 Дочерний поток : 4 Главный поток : 4 Дочерний поток : 3 Дочерний поток : 2 Главный поток : 3 Дочерний поток : 1 Дочерний поток завершен. Главный поток : 2 Главный поток : 1 Главный поток завершен. |
Как упоминалось ранее, в многопоточной программе главный поток исполнения зачастую должен завершаться последним. На самом же деле, если главный поток исполнения завершается раньше дочерних потоков, то исполняющая система Java может «зависнуть», что характерно для некоторых старых виртуальных машин JVМ.
В приведенном выше примере программы гарантируется, что главный поток исполнения завершится последним, поскольку главный поток исполнения находится в состоянии ожидания в течение 1000 миллисекунд в промежутках между последовательными шагами цикла, а дочерний поток исполнения — только 500 миллисекунд. Это заставляет дочерний поток исполнения завершиться раньше главного потока.
Впрочем, в следующих статьях будет показано, как лучше организовать ожидание завершения потоков исполнения.