Класс Thread

Класс java.lang.Thread — основной строительный блок многопоточности в Java. Каждый экземпляр Thread представляет отдельный поток выполнения, который может работать параллельно с главным потоком программы (main). С помощью потоков можно одновременно опрашивать датчики, обрабатывать данные и обновлять интерфейс — это особенно полезно для проектов AlashEd, где, например, один поток читает ультразвуковой датчик, а другой управляет моторами.

Создать поток можно двумя способами: расширить класс Thread и переопределить метод run(), либо передать объект Runnable в конструктор Thread. Метод start() запускает поток — после этого JVM вызывает run() уже в отдельном потоке. Напрямую вызывать run() нельзя — это будет обычный вызов метода в текущем потоке.

Синтаксис

class MyThread extends Thread {
    @Override
    public void run() {
        // код, выполняющийся в новом потоке
    }
}

// запуск
MyThread t = new MyThread();
t.start();   // правильно
// t.run();  // НЕПРАВИЛЬНО — выполнится в текущем потоке

Пример 1. Простейший поток

public class HelloThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println("Поток: шаг " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        HelloThread t = new HelloThread();
        t.start();
        t.join();
        System.out.println("main завершён");
    }
}

Output:

Поток: шаг 1
Поток: шаг 2
Поток: шаг 3
main завершён

Пример 2. Два параллельных потока

public class TwoThreads extends Thread {
    private final String name;

    public TwoThreads(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(name + " - " + i);
            try { Thread.sleep(100); } catch (InterruptedException e) { return; }
        }
    }

    public static void main(String[] args) {
        new TwoThreads("A").start();
        new TwoThreads("B").start();
    }
}

Output (порядок может различаться):

A - 1
B - 1
A - 2
B - 2
A - 3
B - 3

Пример 3. Имя и приоритет потока

public class ThreadInfo extends Thread {
    @Override
    public void run() {
        System.out.println("name=" + getName()
                + ", priority=" + getPriority()
                + ", id=" + getId());
    }

    public static void main(String[] args) {
        ThreadInfo t = new ThreadInfo();
        t.setName("Sensor-Reader");
        t.setPriority(Thread.MAX_PRIORITY);
        t.start();
    }
}

Output:

name=Sensor-Reader, priority=10, id=21

Пример 4. Sleep и пауза

public class SleepDemo extends Thread {
    @Override
    public void run() {
        for (int i = 3; i >= 1; i--) {
            System.out.println("Старт через " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
        System.out.println("Поехали!");
    }

    public static void main(String[] args) {
        new SleepDemo().start();
    }
}

Output:

Старт через 3
Старт через 2
Старт через 1
Поехали!

Пример 5. Daemon-поток

Демон-потоки не препятствуют завершению JVM — они часто используются для фоновых задач: логирование, периодический опрос датчиков.

public class DaemonDemo extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("daemon tick");
            try { Thread.sleep(200); } catch (InterruptedException e) { return; }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DaemonDemo d = new DaemonDemo();
        d.setDaemon(true);
        d.start();
        Thread.sleep(600);
        System.out.println("main завершён, daemon будет убит JVM");
    }
}

Output:

daemon tick
daemon tick
daemon tick
main завершён, daemon будет убит JVM

Подводные камни

Предупреждение

Вызов run() напрямую не создаёт нового потока — код выполнится синхронно в текущем потоке. Используйте только start().

Предупреждение

Один и тот же объект Thread нельзя запустить дважды — повторный вызов start() бросает IllegalThreadStateException.

Совет

Расширять Thread оправдано лишь когда вам действительно нужно переопределить поведение потока. Чаще предпочтительнее реализовать Runnable или использовать ExecutorService — это гибче и тестируемее.

См. также

Примечание

Материал подготовлен на основе раздела «Concurrency» из Oracle Java Tutorials (docs.oracle.com/javase/tutorial/essential/concurrency/) и распространяется в соответствии с Oracle Free Documentation License. Тексты и примеры кода написаны заново на русском языке для AlashEd Wiki.