Создание и выполнение потоков

Последнее обновление: 16.10.2025

Для создания нового потока (потока платформы) в Java мы можем использовать следующие варианты:

  • Реализуация интерфейса Runnable

  • Создание класса, который наследуется от Thread

Рассмотрим оба варианта.

Реализация интерфейса Runnable

Интерфейс Runnable представляет операцию, которая не возвращает никакого результата. Этот интерфейс имеет один метод run:

interface Runnable{
	
	void run();
}

В методе run() собственно определяется весь тот код, который выполняется при запуске потока.

Общий процесс создания и запуска потока выглядит следующим образом:

  1. Определяем реализацию интерфейса Runable. Например:

    Runnable task = ()->{
    
        // здесь действия потока
    };
    

    Поскольку Runnable представляет функциональный интерфейс, который определяет один метод, то объект этого интерфейса мы можем представить в виде лямбда-выражения.

  2. Создаем объект Thread с помощью Runable:

    var t = new Thread(task);
    

    Класс Thread имеет несколько конструкторов. В данном случае используем тот, который принимает объект Runable

  3. Запускаем поток Thread с помощью метода start():

    t.start();
    

Посмотрим на примере:

public class Program {
  
    public static void main(String[] args) {
          
        System.out.println("Main thread started...");

        Runnable task = ()->{       // определяем действия потока
            System.out.println("Runnable started...");

            System.out.println("Runnable finished...");
        };

        var t = new Thread(task);  // определяем поток

        t.start();  // запускаем поток

        System.out.println("Main thread finished...");
    }
}

Здесь в создаваемом потоке просто выводим два сообщения на консоль. Консольный вывод в этом случае может быть следующим:

Main thread started...
Main thread finished...
Runnable started...
Runnable finished...

По консольному выводу видно, что, несмотря на то, что поток запускается до окончания метода main(), но его работа выполняется после последней инструкции в методе main().

При создании потока - объекта Thread с помощью оператора new и вызова конструктора:

var t = new Thread(task);

поток автоматически не запускается. Он лишь переходит в состояние NEW, а программа ещё не начала выполнять его код.

После вызова метода start() поток переходит в состояние RUNNABLE - готовый к запуску. Однако в реальности такой поток еще не выполняется: оне может быть запущен, а может и не запущен. Планировщик потоков решает, предоставить ли потоку время для выполнения. После того, как поток запущен, он может периодически приостанавливаться, чтобы дать другим потокам возможность выполниться. Однако точные детали планирования потоков зависят от операционной системы и от природы потока.

Вместо лямбда-выражения мы можем избрать более долгий путь - определить отдельный класс, который реализует интерфейс Runable. Например:

class MyTask implements Runnable {
    
    public void run(){  // определяем действия потока
         
        System.out.println("Runnable started...");
        System.out.println("Runnable finished...");
    }
} 

public class Program {
  
    public static void main(String[] args) {
          
        System.out.println("Main thread started...");

        Runnable task = new MyTask();

        var t = new Thread(task);  // определяем поток

        t.start();  // запускаем поток

        System.out.println("Main thread finished...");
    }
}

Наследование от класса Thread

Другой способ создания потока представляет наследование от класса Thread. Например:

class MyThread extends Thread {
     
    MyThread(){
        super();  // вызываем конструктор базового класса
    }
     
    public void run(){
         
        System.out.println("MyThread started...");
        System.out.println("MyThread finished...");
    }
}

public class Program {
  
    public static void main(String[] args) {
          
        System.out.println("Main thread started...");

        var t = new MyThread();  // определяем поток

        t.start();  // запускаем поток

        System.out.println("Main thread finished...");
    }
}

Класс потока называется MyThread. В конструкторе класса вызываем один из конструкторов базового класса Thread. И также в MyThread переопределяется метод run(), код которого собственно и будет представлять весь тот код, который выполняется в потоке.

В методе main для запуска потока JThread у него вызывается метод start(), после чего начинается выполнение того кода, который определен в методе run:

var t = new MyThread();  // определяем поток
t.start();  // запускаем поток

Консольный вывод:

Main thread started...
Main thread finished...
MyThread started... 
MyThread finished... 

Запуск массива потоков

Аналогично созданию одного потока мы можем запускать сразу несколько потоков:

class MyTask implements Runnable {
    
    private String name;

    MyTask(String name) { this.name = name;}

    public void run(){  // определяем действия потока
         
        System.out.println(name + " started...");
        System.out.println(name + " finished...");
    }
} 

public class Program {
  
    public static void main(String[] args) {
          
        System.out.println("Main thread started...");

        for(int i=1; i < 6; i++){

            var task = new MyTask("MyTask_" + i);
            new Thread(task).start();
        }

        System.out.println("Main thread finished...");
    }
}

В данном случае для разграничения потоков в класс MyTask через конструктор передается имя потока. А в цикле при запуске потока формируем имя на основе номера потока.

Консольный вывод в данном случае недетерминирован. Например, он может быть следующим:

Main thread started...
Main thread finished...
MyTask_2 started...
MyTask_1 started...
MyTask_1 finished...
MyTask_4 started...
MyTask_3 started...
MyTask_3 finished...
MyTask_2 finished...
MyTask_4 finished...
MyTask_5 started...
MyTask_5 finished...

Но по данному выводу мы видимо, что потоки вне зависимости от запуска работают фактически одновременно, как бы параллельно

Конструкторы Thread

Для данного класса Thread определенно несколько конструкторов, которые можно использовать. Но я отмечу один из них:

Thread(ThreadGroup group, Runnable task, String name)

Параметры конструктора:

  • group: группа потока. Если значение равно null, то в качестве группы устанавливается группа текущего потока.

  • task: объект Runnable, который собственно и определяет выполняемые в потоке действия. Если значение равно null, то вызывается метод run() этого потока.

  • name: имя создаваемого потока

Я взял именно этот вариант конструктора, потому что большинство других неявно вызывают его. Например, следующий вариант конструктора

Thread(Runnable task, String name)

Фактически имеет тот же эффект, что и вызов

Thread (null, task, name)

А вариант конструктора

Thread(Runnable task)

Фактически аналогичен вызову

Thread (null, task, gname)

Здесь gname - автоматически генерируемое имя в виде "Thread-"+n, где n - целое число.

Например, модифицтруем предыдущую программу:

class MyTask implements Runnable {

    public void run(){  // определяем действия потока
         
        // получаем имя текущего потока
        String name = Thread.currentThread().getName();
        System.out.println(name + " started...");
        System.out.println(name + " finished...");
    }
} 

public class Program {
  
    public static void main(String[] args) {
          
        System.out.println("Main thread started...");

        for(int i=1; i < 6; i++){

            var task = new MyTask();
            new Thread(task).start();
        }

        System.out.println("Main thread finished...");
    }
}

Здесь в классе MyTask в методе run() с помощью вызова Thread.currentThread().getName() получаем имя потока, которое автоматически ему присваивается. Далее на консоли мы увидим автосгенерированные имена потоков:

Main thread started...
Main thread finished...
Thread-2 started...
Thread-0 started...
Thread-3 started...
Thread-4 started...
Thread-0 finished...
Thread-1 started...
Thread-2 finished...
Thread-3 finished...
Thread-4 finished...
Thread-1 finished...

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

for(int i=1; i < 6; i++){

    var task = new MyTask();
    new Thread(task, "MyTask_" + i).start();
}

При консольном выводе мы увидим назначенные вручную потокам имена:

Main thread started...
Main thread finished...
MyTask_1 started...
MyTask_4 started...
MyTask_1 finished...
MyTask_3 started...
MyTask_4 finished...
MyTask_3 finished...
MyTask_5 started...
MyTask_5 finished...
MyTask_2 started...
MyTask_2 finished...
Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850
Morty Proxy This is a proxified and sanitized view of the page, visit original site.