Реализация Runnable

Самый простой способ создания потока — это объявление класса, реализующего интерфейс Runnable. Runnable абстрагирует единицу исполняемого кода. Вы можете конструировать поток из любого объекта, реализующего интерфейс Runnable. Чтобы реализовать Runnable, класс должен объявить единственный метод run ():

public void run()

Внутри run () вы определяете код, который, собственно, составляет новый поток. Важно понимать, что run () может вызывать другие методы, использовать другие классы, объявлять переменные — точно так же, как это делает главный поток. Единственным отличием является то, что run () устанавливает точку входа для другого, параллельного потока внутри вашей программы. Этот поток завершится, когда run () вернет управление.

После того как будет объявлен класс, реализующий интерфейс Runnable, вы создадите объект типа Thread из этого класса. В Thread определено несколько конструкторов. Тот, который должен использоваться в данном случае, выглядит следующим образом:

Thread(Runnable объект_потока, String имя_потока)

В этом конструкторе объект_потока — это экземпляр класса, реализующего интерфейс Runnable. Он определяет, где начнется выполнение потока. Имя нового потока передается в иия_потока.

После того, как новый поток будет создан, он не запускается до тех пор, пока вы не вызовете метод start (), объявленный в классе Thread. По сути, start () выполняет вызов run (). Метод start () показан ниже:

void start ()

Рассмотрим Пример, создающий новый поток и запускающий его выполнение:

// Создание второго потока.
class NewThread implements Runnable {
Thread t;
NewThread () {
// Создать новый, второй поток
t = new Threadfthis, "Демонстрационный поток");
System.out.println("Дочерний поток создан: " + t) ;
t.startO; // Запустить поток
}
// Точка входа второго потока.
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("Главный поток завершен.");
}
}

Внутри конструктора NewThread в следующем операторе создается новый объект Thread:

t = new Thread(this, "Демонстрационный поток");

Передача this в первом аргументе означает, что вы хотите, чтобы новый поток вызвал run () метод объекта this. Далее вызывается start (), чем запускается выполнение потока, начиная с метода run (). Это запускает цикл for дочернего потока. После вызова start () конструктор NewThread возвращает управление main (). Когда главный поток продолжает свою работу, он входит в свой цикл for. После этого оба потока выполняются параллельно, разделяя ресурсы центрального процессора, вплоть до завершения своих циклов. Вывод, генерируемый этой программой, показан ниже (ваш вывод может варьироваться, в зависимости от скорости процессора и загрузки).

Дочерний поток: Thread[Демонстрационный поток,5,main]
Главный поток: 5
Дочерний поток: 5
Дочерний поток: 4
Главный поток: 4
Дочерний поток: 3
Дочерний поток: 2
Главный поток: 3
Дочерний поток: 1
Дочерний поток завершен.
Главный поток: 2
Главный поток: 1
Главный поток завершен.

Как уже упоминалось ранее, в многопоточной программе часто главный поток должен завершать выполнение последним. Фактически, для некоторых старых виртуальных машин Java (JVM), если главный поток завершается до завершения дочерних потоков, то исполняющая система Java может "зависнуть". Предыдущая программа гарантирует, что главный поток завершится последним, поскольку главный поток "спит" 1000 миллисекунд между итерациями цикла, а дочерний поток "спит" только 500 миллисекунд. Это заставляет дочерний поток завершиться раньше главного. Но далее вы узнаете лучший способ ожидания завершения потоков.