Share This
Связаться со мной
Крути в низ
Categories
//Потоки в Java

Потоки в Java

В чем заключается «магия» многопоточности? Как создать поток и чем он отличается от процесса? Как процессор обрабатывает потоки?

potoki v java 6b93760 - Потоки в Java

Независимо от того, какой язык вы используете для написания своих программ, по умолчанию все они являются последовательными. То есть все инструкции, которые мы пишем, последовательно выполняются операционной системой. Следующая строка кода не может начать свое выполнение до завершения текущей и ждет, пока текущая строка не завершит свое выполнение. Если в программе есть вызов API к удаленному серверу, то программа блокируется до тех пор, пока не получит ответ к запросу. Если выполнение вызова занимает несколько минут, то нам придётся также ждать ответа программы. Программа в таком случае просто блокируется и не будет отвечать на дальнейшие команды.

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

Существует множество определений многопоточности:

  • Многопоточность — это способность процессора независимо выполнять процессы или потоки. Все программы выполняются потоками. Поток — это легковесный подпроцесс, наименьшая единица обработки.
  • Многопоточность — это процесс одновременного выполнения нескольких потоков.

Преимущества многопоточности

  • с использованием многопоточности можно разрабатывать более адаптивные приложения: мы можем выполнять несколько операций одновременно, например, скачивание некоторых ресурсов и общение в чате одновременно;
  • мы можем добиться лучшего использования ресурсов: по умолчанию программа Java является однопоточной. Может быть несколько ядер процессора, которые можно использовать, применяя многопоточность;
  • общая производительность может быть увеличена в несколько раз.

Недостатки многопоточности

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

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека джависта» Интересно, перейти к каналу

Многопоточность в Java

Java предоставляет базовый класс для создания потоков — класс Thread. Существует два способа создания потоков: либо наследование класса Thread и переопределение метода run(), либо реализация интерфейса Runnable и передача его реализации классу Thread в качестве аргумента конструктора. Давайте рассмотрим пример с использованием обоих способов.

Создание потоков путем наследования класса Thread

         public class App {      public static void main(String[] args) {         var ferrari = new Car("Ferrari");         var bmw = new Car("BMW");         ferrari.start();         bmw.start();         System.out.println("Method continues execution... Main method is executed by thread " + Thread.currentThread().getName());     }  }  class Car extends Thread {      private final String model;      public Car(String model) {         this.model = model;     }      @Override     public void run() {         try {             Thread.sleep(1000);         } catch (InterruptedException exception) {             exception.printStackTrace();         }         System.out.println("Car " + model + " is being driven by thread " + Thread.currentThread().getName());     }  }     

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

Thread.sleep(1000) — останавливает этот поток на заданный период времени (в миллисекундах). В main-методе мы создаем два экземпляра (ferrari, bmw) класса Car и вызываем метод start() для каждого из них. Затем выводим какое-нибудь сообщение. По умолчанию всякий раз, когда запускается любая Java-программа, она выполняется основным потоком. Запуск этой программы дает следующий вывод.

potoki v java f344378 - Потоки в Java

Вывод программы

Как видим из вывода, вывод сообщения, которое написали в методе main — последняя команда в программе, но она выводится в консоль первой и не ждёт выполнения вызовов методов — ferrari.start() и bmw.start(). Это и есть магия многопоточности. Сколько бы времени ни потребовалось для выполнения методов — ferrari.start() и bmw.start(), поток main дальше выполняется и не ждёт их завершения.

Создание потоков путем реализации интерфейса Runnable

         public class App {      public static void main(String[] args) {         var ferrari = new Car("Ferrari");         var bmw = new Car("BMW");         var ferrariThread = new Thread(ferrari, "Ferrari-Thread");         var bmwThread = new Thread(bmw, "BMW-Thread");         ferrariThread.start();         bmwThread.start();         System.out.println("Method continues execution... Main method is executed by thread " + Thread.currentThread().getName());     }  }  class Car implements Runnable {      private final String model;      public Car(String model) {         this.model = model;     }      @Override     public void run() {         try {             Thread.sleep(1000);         } catch (InterruptedException exception) {             exception.printStackTrace();         }         System.out.println("Car " + model + " is being driven by thread " + Thread.currentThread().getName());     }  }     

Приведенный выше фрагмент кода выполняет ту же логику, которую мы обсуждали выше, однако он немного отличается от предыдущего фрагмента кода. В этом случае мы реализовываем интерфейс Runnable и переопределяем его метод run() и передаём их конструктору класса Thread. Один из конструкторов класса Thread принимает интерфейс Runnable в качестве одного из своих аргументов. Вывод программы аналогичен

potoki v java 54612b1 - Потоки в Java

Вывод программы

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

Что произойдет, если мы вызовем метод run() класса Thread?

         public class App {      public static void main(String[] args) {         var ferrari = new Car("Ferrari");         var bmw = new Car("BMW");         ferrari.run();         bmw.run();         System.out.println("Method continues execution... Main method is executed by thread - " + Thread.currentThread().getName());     }  }  class Car extends Thread {      private final String model;      public Car(String model) {         this.model = model;     }      @Override     public void run() {         try {             Thread.sleep(1000);         } catch (InterruptedException exception) {             exception.printStackTrace();         }         System.out.println("Car " + model + " is being driven by thread " + Thread.currentThread().getName());     }  }     

Давайте рассмотрим приведенный выше фрагмент кода. В этом примере аналогичным образом мы создали класс Car, который наследует класс Thread, переопределили метод run(), создали два экземпляра класса Car (ferrari, bmw). Вместо вызова метода start() мы вызвали метод run() у экземпляров (ferrari, bmw). Запуск этой программы дает следующий результат.

potoki v java deb7b5b - Потоки в Java

Вывод программы

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

Процессы и потоки

Термины «процесс» и «поток» повсеместно используются, когда речь идет о многопоточности. Давайте подробно рассмотрим эти две концепции.

Процесс — это исполняемая программа, или, другими словами, просто запущенная программа

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

Поток — это легкий процесс

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

Многопоточность и алгоритм разделения времени

Представьте, что у вас есть устройство с одним ядром и запущенное приложение, которое требует k потоков (k > 1). В этом случае одному процессору приходится обрабатывать k потоков. Как процессор обрабатывает k – потоков? Вот где алгоритм time-slicing делает всю магию.

При наличии нескольких потоков время обработки одноядерного процессора распределяется между процессами и потоками. Все потоки планируются процессором случайным образом c помощью thread scheduler, и каждый поток получает минимальное количество времени для выполнения. Как только выделенное время истекает, другой поток получает свою часть процессорного времени и начинает свою часть выполнения. Этот процесс выделения процессором времени потокам продолжается до тех пор, пока все потоки не завершат свое выполнение. Это называется алгоритмом квантования времени (time slicing algorithm). Под капотом потоки выполняются последовательно, однако они выполняются настолько быстро, что у нас возникает ощущение параллельного выполнения. Это называется simulated or fake concurrency..

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

***

Материалы по теме

  • ☕ Изучение Java с нуля: что должен знать junior?
  • ☕ Топ-10 книг по Java, вышедших за последние два года

  • 1 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Свежие комментарии

    Рубрики

    About Author 01.

    blank
    Roman Spiridonov

    Моя специальность - Back-end Developer, Software Engineer Python. Мне 39 лет, я работаю в области информационных технологий более 5 лет. Опыт программирования на Python более 3 лет. На Django более 2 лет.

    Categories 05.

    © Speccy 2022 / All rights reserved

    Связаться со мной
    Close