Многопоточное программирование и класс Thread

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

Большинство языков программирования поддерживают такую важную функциональность как многопоточность, и Java в этом плане не исключение. При помощи многопоточности мы можем выделить в приложении несколько потоков, которые будут выполнять различные задачи одновременно. Если у нас, допустим, графическое приложение, которое посылает запрос к какому-нибудь серверу или считывает и обрабатывает огромный файл, то без многопоточности у нас бы блокировался графический интерфейс на время выполнения задачи. А благодаря потокам мы можем выделить отправку запроса или любую другую задачу, которая может долго обрабатываться, в отдельный поток. Поэтому большинство реальных приложений, которые многим из нас приходится использовать, практически не мыслимы без многопоточности.

Стоит отметить, что начиная с версии 21 в языке Java есть два типа потоков: потоки платформы (platform thread) и виртуальные потоки (virtual thread). Поток платформы представляет поток, предоставляемый операционной системой, которая планирует его выполнение. Виртуальные потоки выполняются в потоках платформы и планируются средой выполнения Java.

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

Класс Thread

В языке Java за работу с потоками отвечает класс Thread. Фактически этот класс и представляет функциональность потока. И чтобы создать новый поток, нам надо создать объект этого класса.

Но все потоки необходимо создавать вручую. Когда запускается программа, начинает работать главный поток этой программы, в котором и выполняется метод main(). От этого главного потока порождаются все остальные дочерние потоки.

Получение текущего потока

С помощью статического метода Thread.currentThread() мы можем получить текущий поток выполнения в виде объекта Thread:

public class Program {
  
    public static void main(String[] args) {
          
        // получаем текущий поток
        Thread t = Thread.currentThread(); 
        System.out.println(t); // Thread[#3,main,5,main]
    }
}

В данном случае мы получаем текущий поток, в котором запущен метод main, в переменную t и выводим его на консоль. В итоге мы получим консольный вывод следующего вида:

Thread[#3,main,5,main]

Что значит этот вывод? Этот вывод сформирован с помощью метода toString() и означает следующее:

идентификатор_потока, имя_потока, приоритет, группа потока

То есть мы получаем:

  • идентификатор потока: 3

  • имя потока: main

  • приоритет потока: 5

  • группа потока: main

Стоит отметить, что с помощью статического метода Thread.getAllStackTraces() мы можем получить трассировку стека для всех активных потоков в программе в виде словаря Map<Thread, StackTraceElement[]>. Например, выведем на консоль все активные потоки:

public class Program {
  
    public static void main(String[] args) {
          
        // получаем только потоки без трасировки стека
        var threads = Thread.getAllStackTraces().keySet(); 

        for(var thread : threads){
            System.out.println(thread); 
        }
    }
}

В этом случае мы получим консольный вывод что-то вроде следующего:

Thread[#3,main,5,main]
Thread[#15,Reference Handler,10,system]
Thread[#16,Finalizer,8,system]
Thread[#32,Notification Thread,9,system]
Thread[#17,Signal Dispatcher,9,system]
Thread[#33,Common-Cleaner,8,InnocuousThreadGroup]

Получение информации о потоках

Класс Thread предоставляет ряд методов для управления и получения состояния потоков. Отмечу некоторые из этих методов:

  • String getName(): возвращает имя потока

  • void setName(String name): устанавливает имя потока

  • int getPriority(): возвращает приоритет потока

  • void setPriority(int proirity): устанавливает приоритет потока. Приоритет является одним из ключевых факторов для выбора системой потока из кучи потоков для выполнения. В этот метод в качестве параметра передается числовое значение приоритета - от 1 до 10. По умолчанию главному потоку выставляется средний приоритет - 5.

  • StackTraceElement[] getStackTrace(): возвращает трассировку стека текущего потока в виде массива объектов StackTraceElement

  • void setDaemon(boolean on): устанавливает, является ли поток фоновым

  • boolean isAlive(): возвращает true, если поток активен

  • boolean isInterrupted(): возвращает true, если поток был прерван

  • boolean isDaemon(): возвращает true, если поток является фоновым (потоком-демоном)

  • boolean isVirtual(): возвращает true, если поток является виртуальным

  • long threadId(): возвращает идентификатор текущего потока

И также можно выделить ряд методов по управлению потоками:

  • void join(): ожидает завершение потока

  • void run(): определяет действия, которые собственно и выполняет поток

  • static void sleep(long millis): приостанавливает поток на заданное количество миллисекунд

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

  • static Thread startVirtualThread(Runnable task): запускает или ставит в очередь на запуск виртуальный поток

Например, получим информацию о текущем потоке (главном потоке программы):

public class Program {
  
    public static void main(String[] args) {
          
        // получаем текущий поток
        Thread t = Thread.currentThread(); 
        // получаем его свойства

        System.out.println("Id: " + t.threadId()); 
        System.out.println("Name: " + t.getName()); 
        System.out.println("Priority: " + t.getPriority()); 
        System.out.println("State: " + t.getState()); 
        System.out.println("isAlive: " + t.isAlive()); 
        System.out.println("isDaemon: " + t.isDaemon()); 
        System.out.println("isVirtual: " + t.isVirtual()); 
    }
}

В данном случае мы получим следующий консольный вывод:

Id: 3
Name: main
Priority: 5
State: RUNNABLE
isAlive: true
isDaemon: false
isVirtual: false

Рассмотрим некоторые свойства потоков подробнее.

Состояние потоков

Потоки могут находиться в следующих 6 состояниях:

  • NEW: поток создан, но еще не запущен

  • RUNNABLE: поток выполняется

  • BLOCKED: поток заблокирован в ожидании блокировки монитора

  • WAITING: поток неопределенно долго ожидает, пока другой поток выполнит определенное действие

  • TIMED_WAITING: поток, ожидающий выполнения действия другим потоком в течение заданного времени

  • TERMINATED: поток завершил выполнение

В любой момент времени поток может находиться только в одном состоянии. Эти состояния представляют собой состояния виртуальной машины, которые не отражают состояния потоков операционной системы.

Приоритеты потоков

Каждый поток имеет приоритет. Приоритеты представляют числа от 1 до 10. Для их описания Java представляет три константы:

  • MIN_PRIORITY: минимальный приоритет, соответствует числу 1

  • MAX_PRIORITY: максимальный приоритет, соответствует числу 10

  • NORM_PRIORITY: стандартный приоритет, соответствует числу 5

По умолчанию поток наследует приоритет потока, который его создал. Но теоретически можно увеличить или уменьшить приоритет любого потока платформы с помощью метода setPriority(). В частности, можно установить приоритет на любое значение между MIN_PRIORITY (1) и MAX_PRIORITY (10).

Всякий раз, когда у планировщика потоков платформы есть возможность выбрать новый поток, он отдает предпочтение потокам с более высоким приоритетом. Однако приоритеты потоков сильно зависят от системы. Поскольку виртуальная машина Java опирается на реализацию потоков текущей платформы, приоритеты потоков Java сопоставляются с уровнями приоритета текущей платформы, которая может иметь больше или меньше уровней приоритета потоков.

Например, в Windows имеется семь уровней приоритета. Некоторые приоритеты Java будут соответствовать одному и тому же уровню операционной системы. В виртуальной машине OpenJDK для Linux приоритеты потоков вообще игнорируются — все потоки имеют одинаковый приоритет. Приоритеты потоков могли быть полезны в ранних версиях Java, которые не использовали потоки операционных систем. Вам не следует использовать их в настоящее время.

Для обратной совместимости все виртуальные потоки имеют приоритет NORM_PRIORITY. Так, у главного потока программы по умолчанию приоритет - 5. Попытка изменить приоритет не имеет никакого эффекта.

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