English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Основной курс Java

Java Управление потоком

Java Массивы

Java Ориентированность на объекты (I)

Java Ориентированность на объекты (II)

Java Ориентированность на объекты (III)

Обработка исключений Java

Java Список (List)

Java Queue (очередь)

Java Map коллекция

Java Set коллекция

Java Вход/Выход (I/O)

Java Reader/Writer

Другие темы Java

Java параллельное программирование на многопоточности

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

Многоядерные потоки - это особая форма многозадачности, но они используют меньше ресурсов.

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

Многоядерные потоки позволяют программистам пишите эффективные программы для максимального использования CPU.

Жизненный цикл потока

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

На рисунке показан полный жизненный цикл потока.

  • Состояние создания:

    используется new ключевое слово и Thread Класс или его подкласс создает объект потока, и этот объект потока переходит в состояние создания. Он остается в этом состоянии до тех пор, пока программа start() этот поток.

  • Состояние готовности:

    После вызова метода start() объект потока переходит в состояние готовности. Потоки в состоянии готовности находятся в очереди готовности и ждут调度 потока JVM.

  • Состояние выполнения:

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

  • Состояние блокировки:

    Если поток выполняет методы sleep (сон), suspend (приостановка) и т.д., после потери所占ываемых ресурсов поток переходит из состояния выполнения в состояние блокировки. После истечения времени сна или получения ресурсов устройства поток может вернуться в состояние готовности. Это можно разделить на три типа:

    • Блокировка ожидания: поток в состоянии выполнения выполняет метод wait(), чтобы перейти в состояние блокировки ожидания.

    • Синхронная блокировка: поток не может получить блокировку synchronized (поскольку блокировка занята другим потоком).

    • Другие блокировки: когда вызывается метод sleep() или join() и выполняется запрос I/O, поток переходит в состояние блокировки. Когда время ожидания sleep() истекает, ожидание завершения потока join() или истечение времени ожидания, или завершение обработки I/O, поток возвращается в состояние готовности.

  • Состояние смерти:

    Если поток в состоянии выполнения завершает задачу или наступают другие условия завершения, поток переходит в состояние завершения.

Порядок приоритета потоков

Каждый поток Java имеет приоритет, что помогает операционной системе определить порядок调度 потоков.

Порядок приоритета потоков Java - это целое число, значение которого варьируется от 1 (Thread.MIN_PRIORITY) до 10 (Thread.MAX_PRIORITY).

По умолчанию, каждому потоку назначается приоритет NORM_PRIORITY (5).

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

Создание потока

Java предоставляет три способа создания потоков:

  • Создание потока через реализацию интерфейса Runnable

  • Создание потока через наследование от класса Thread

  • Создание потока через Callable и Future

Создание потока через реализацию интерфейса Runnable

Самый простой способ создания потока - это создание класса, реализующего интерфейс Runnable.

Чтобы реализовать интерфейс Runnable, классу нужно выполнить только один вызов метода run(), как показано ниже:

public void run()

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

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

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

Thread(Runnable threadOb, String threadName);

Здесь threadOb - это пример класса, реализующего интерфейс Runnable, и threadName определяет имя нового потока.

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

void start();

Ниже приведен пример создания потока и начала его выполнения:

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo(String name) {
      threadName = name;
      System.out.println("Создание " + threadName);
   {}
   
   public void run() {
      System.out.println("Запуск " + threadName);
      try {
         for (int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Позвольте потоку поспать немного
            catch (InterruptedException e) {
         {}
      }
         System.out.println("Thread " + threadName + " прерван.");
      {}
      System.out.println("Thread " + threadName + " выходит.");
   {}
   
   public void start() {
      System.out.println("Запуск " + threadName);
      if (t == null) {
         t = new Thread(this, threadName);
         t.start();
      {}
   {}
{}
 
public class TestThread {
 
   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo("Thread-1");
      R1.start();
      
      RunnableDemo R2 = new RunnableDemo("Thread-2");
      R2.start();
   {}   
{}

Результат выполнения программы, скомпилированной выше, таков:

Создание потока-1
Запуск потока-1
Создание потока-2
Запуск потока-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Создание потока через наследование от Thread

Второй способ создания потока - это создание нового класса, который наследуется от класса Thread, и создание экземпляра этого класса.

Класс-наследник должен переопределить метод run(), который является точкой входа для нового потока. Он также должен вызывать метод start(), чтобы начать выполнение.

Этот метод, хотя и перечислен как один из способов реализации многопоточности, по сути является примером, реализующим интерфейс Runnable.

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo(String name) {
      threadName = name;
      System.out.println("Создание " + threadName);
   {}
   
   public void run() {
      System.out.println("Запуск " + threadName);
      try {
         for (int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Позвольте потоку поспать немного
            catch (InterruptedException e) {
         {}
      }
         System.out.println("Thread " + threadName + " прерван.");
      {}
      System.out.println("Thread " + threadName + " выходит.");
   {}
   
   public void start() {
      System.out.println("Запуск " + threadName);
      if (t == null) {
         t = new Thread(this, threadName);
         t.start();
      {}
   {}
{}
 
public class TestThread {
 
   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo("Thread-1");
      T1.start();
      
      ThreadDemo T2 = new ThreadDemo("Thread-2");
      T2.start();
   {}   
{}

Результат выполнения программы, скомпилированной выше, таков:

Создание потока-1
Запуск потока-1
Создание потока-2
Запуск потока-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.

Метод Thread

В таблице перечислены некоторые важные методы класса Thread:

НомерОписание метода
                1public void start()
Включить выполнение этого потока.;Java Виртуальная машина вызывает метод run потока.
                2public void run()
Если поток был создан с использованием независимого объекта Runnable, вызывается метод run объекта Runnable; в противном случае,该方法 не выполняет никаких действий и возвращает.
                3public final void setName(String name)
Изменить имя потока, чтобы оно соответствовало параметру name.
                4public final void setPriority(int priority)
 Изменить приоритет потока.
                5public final void setDaemon(boolean on)
Отметить этот поток как демонстративный поток или пользовательский поток.
                6public final void join(long millisec)
Ожидать завершения этого потока в течение longest of millis миллисекунд.
                7public void interrupt()
Интеррумпировать поток.
                8public final boolean isAlive()
Тестировать, находится ли поток в активном состоянии.

Тестировать, находится ли поток в активном состоянии. Указанные методы вызываются объектом Thread. Следующие методы являются статическими методами класса Thread.

НомерОписание метода
                1public static void yield()
Приостановить текущий выполняющийся поток объекта и выполнить другой поток.
                2public static void sleep(long millisec)
В течение указанного миллисекундного интервала позволяет текущий выполняющийся поток休眠(приостановить выполнение), эта операция зависит от точности и точности системного таймера и планировщика.
                3public static boolean holdsLock(Object x)
Возвращает true, только если текущий поток удерживает монитор на指定的 объекте.
                4public static Thread currentThread()
Возвращает ссылку на объект потока, который в настоящее время выполняется.
                5public static void dumpStack()
Печатает трассировку стека текущего потока в стандартный поток ошибок.

Пример онлайн

Программа ThreadClassDemo демонстрирует некоторые методы класса Thread:

// Имя файла: DisplayMessage.java
// Создание потока через реализацию интерфейса Runnable
public class DisplayMessage implements Runnable {
   private String message;
   
   public DisplayMessage(String message) {
      this.message = message;
   {}
   
   public void run() {
      while(true) {
         System.out.println(message);
      {}
   {}
{}

GuessANumber.java файл кода:

// Имя файла: GuessANumber.java
// Создание потока через наследование класса Thread
 
public class GuessANumber extends Thread {
   private int number;
   public GuessANumber(int number) {
      this.number = number;
   {}
   
   public void run() {
      int counter = 0;
      int guess = 0;
      do {
         guess = (int) (Math.random() * 100 + 1);
         System.out.println(this.getName() + " попытки " + guess);
         counter++;
      }
      System.out.println("** Правильно!" + this.getName() + "в" + counter + "попытках.**");
   {}
{}

ThreadClassDemo.java файл кода:

// 文件名 : ThreadClassDemo.java
public class ThreadClassDemo {
 
   public static void main(String [] args) {
      Runnable hello = new DisplayMessage("Hello");
      Thread thread1 = new Thread(hello);
      thread1.setDaemon(true);
      thread1.setName("hello");
      System.out.println("Starting hello thread...");
      thread1.start();
      
      Runnable bye = new DisplayMessage("Goodbye");
      Thread thread2 = new Thread(bye);
      thread2.setPriority(Thread.MIN_PRIORITY);
      thread2.setDaemon(true);
      System.out.println("Starting goodbye thread...");
      thread2.start();
 
      System.out.println("Starting thread3...");
      Thread thread3 = new GuessANumber(27);
      thread3.start();
      try {
         thread3.join();
      }catch(InterruptedException e) {
         System.out.println("Thread interrupted.");
      {}
      System.out.println("Starting thread4...");
      Thread thread4 = new GuessANumber(75);
      
      thread4.start();
      System.out.println("main() is ending...");
   {}
{}

运行结果如下,每一次运行的结果都不一样。

Starting hello thread...
Запуск потока прощания...
Привет
Привет
Привет
Привет
Привет
Привет
Прощание
Прощание
Прощание
Прощание
Прощание
......

Создание потоков через Callable и Future

  • 1. Создание класса, реализующего интерфейс Callable, и реализация метода call(), который будет выполняться в потоке и имеет возвращаемое значение.

  • 2. Создание примера класса, реализующего интерфейс Callable, и использование класса FutureTask для обертки объекта Callable, который封装ирует возвращаемое значение метода call().

  • 3. Использование объекта FutureTask в качестве目标是 создания и запуска нового потока.

  • 4. Вызов метода get() объекта FutureTask для получения возвращаемого значения после завершения выполнения под线程а.

public class CallableThreadTest implements Callable<Integer> {
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<>(ctt);  
        for (int i = 0; i < 100; i++)  
        {  
            System.out.println(Thread.currentThread().getName() + " " + " Worth of loop variable i " + i);  
            if (i == 20)  
            {  
                new Thread(ft, "Струна с возвращаемым значением").start();  
            {}  
        {}  
        try  
        {  
            System.out.println("Возвратное значение под线程а: " + ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        {}  
  
    {}
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for (; i < 100; i++)  
        {  
            System.out.println(Thread.currentThread().getName() + " " + i);  
        {}  
        return i;  
    {}  
{}

Сравнение трех способов создания потоков

  • 1. При создании многопоточности с помощью реализации интерфейсов Runnable, Callable, потоковый класс реализует интерфейс Runnable или Callable, и может наследовать другие классы.

  • 2. При создании многопоточности с помощью наследования класса Thread, написание просто, если вам нужно получить текущий поток, вам не нужно использовать метод Thread.currentThread(), просто используйте this, чтобы получить текущий поток.

Основные концепции потоков

При параллельном программировании на многопоточности вам необходимо понять несколько концепций:

  • Синхронизация потоков

  • Коммуникация между потоками

  • Тяжелый deadlock потока

  • Управление потоками: приостановка, остановка и восстановление

Использование многопоточности

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

Использование многопоточности позволяет создавать очень эффективные программы. Однако обратите внимание, что если вы создадите слишком много потоков, эффективность выполнения программы实际上是 снижается, а не увеличивается.

Помните, также важна стоимость переключения контекста, если вы создадите слишком много потоков, время, затраченное на переключение контекста CPU, будет больше времени выполнения программы!