Что такое нить?

Поток - это поток исполнения. Путь, по которому процесс выполняет свою задачу. В java основным методом, который мы используем, является сам поток. Он называется основным потоком. Каждый раз, когда мы запускаем программу Java с ключевым словом Java в командной строке, создается новый поток (основной поток).

JVM (виртуальная машина Java) может поддерживать более одного потока одновременно. Если программа Java запускается с использованием более чем одного потока, она называется многопоточным приложением. Внутри JVM есть компонент под названием Thread Scheduler, он позаботится о выполнении потоков. Теперь давайте посмотрим на многопоточность и ее преимущества.

Что такое многопоточность?

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

Посмотрите на следующую задачу, которую должна выполнить программа.

Read Customer Details
Store Customer Details on Database
Read Item Details
Store Item Details on Database

В приведенном выше примере. Есть 4 задания; первые два посвящены покупателям, а два других - товарам. Эти задачи не зависят друг от друга. Поскольку эти задачи не зависят друг от друга, эти задачи можно выполнять параллельно. Таким образом, поток для обработки сведений о клиентах и ​​другой поток для обработки сведений об элементах смогут повысить эффективность этой программы.

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

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

Жизненный цикл резьбы

Нить будет находиться во множестве состояний от своего рождения до смерти. Цикл, который проходит от начала до конца, известен как жизненный цикл потока. Это 7 основных состояний, в которых поток будет находиться в течение своего времени жизни.

1. Новое состояние - ›Поток будет в этом состоянии при создании.

2. Выполнение - ›Поток будет в этом состоянии, когда в потоке будет вызван метод start ().

3. Выполняется - ›Поток будет в этом состоянии, пока выполняет свои операции.

4. Ожидание - ›В этом состоянии поток не выполняет никаких операций. Он будет просто ждать бесконечно, пока не будет прерван или пока конкретный поток не завершит свои операции.

5. Время ожидания- ›Поток будет находиться в состоянии ожидания, пока не истечет время ожидания или не будет прервано.

6. Заблокирован - ›Поток будет в этом состоянии до тех пор, пока блокировка не будет снята другим потоком или пока этот поток не будет прерван.

7. Завершено - ›Поток перейдет в это состояние, когда закончит свою работу.

Потоки будут перемещаться между этими состояниями выполнения, выполнения, ожидания по времени, ожидания и блокировки. Как только поток достигает мертвого состояния, он не может вернуться в предыдущее состояние.

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

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

Давайте рассмотрим сценарий. У нас есть класс java под названием Manager , который расширяет Employee. В этом случае мы не можем расширить класс Manager классом Thread, поскольку Java в настоящий момент не поддерживает множественное наследование. Вместо этого мы можем реализовать класс Runnable для класса Manager, чтобы сделать его классом, поддерживающим потоки. Их реализация будет объяснена в следующих разделах.

Расширение класса потока

Хорошо, сначала давайте посмотрим, как создать класс, расширив класс Thread.

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

Примечание. Метод CurrentThread (). GetName () вернет имя потока для потока, выполняющего операцию печати. Что легко увидеть на выходе.

В строке 9 инициализируется объект Odd printer Thread. (Эта ветка сейчас находится в новом состоянии.)

Чтобы запустить этот поток, нам нужно будет вызвать метод oddPrinter.start ();

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

main  is printing
main  is printing
main  is printing
Thread-0 :1
Thread-0 :3
Thread-0 :5
Thread-0 :7
Thread-0 :9
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
Thread-0 :11
main  is printing
main  is printing
Thread-0 :13
Thread-0 :15
Thread-0 :17
Thread-0 :19
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing

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

Перед запуском поток проходит через другие процессы, метод start в родительском классе Thread проверяет различные условия, а затем только вызывает метод запуска и запускает новый поток. А также планировщик потоков, который находится внутри JVM, решает, какой поток запускать. Поскольку JVM зависит от JRE, она будет отличаться в зависимости от используемой операционной системы. Также main - это основной поток, а Thread-0 - поток oddPrinter (дочерний поток).

Вы можете подумать, зачем вызывать метод запуска, если мы можем просто вызвать метод запуска напрямую. Давайте сделаем это и посмотрим на результат.

main :1
main :3
main :5
main :7
main :9
main :11
main :13
main :15
main :17
main :19
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing

Как видно из приведенной выше информации, новый поток не создается, вместо этого основной поток является единственным запущенным потоком. Вот почему после того, как main закончит печатать нечетные числа, он переходит к своему собственному методу печати. Причина этого в том, что метод start () заботится о процессах, необходимых для создания нового потока, а затем вызывает метод run для вновь созданного потока для выполнения своих операций.

Теперь давайте переопределим метод start () и посмотрим, что произойдет.

Odd Printer Thread is starting
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing

Как видно из вывода, новый поток не создается. Итак, причина этого - основная концепция ООП. Всякий раз, когда мы вызываем метод, JVM будет проверять, находится ли этот метод внутри дочернего класса, если его нет в дочернем классе, она пойдет и проверит родительский. Поскольку мы переопределяем метод, будет вызываться только метод в дочернем элементе. Итак, давайте вызовем метод super.start () из метода переопределения и посмотрим, что произойдет.

Thread is starting
Thread-0 :1
Thread-0 :3
main  is printing
main  is printing
main  is printing
main  is printing
Thread-0 :5
main  is printing
main  is printing
Thread-0 :7
Thread-0 :9
Thread-0 :11
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
Thread-0 :13
Thread-0 :15
Thread-0 :17
Thread-0 :19

Запускается новый поток, и он работает как положено.

Теперь допустим, что мы перегружаем метод run (), и что дальше?

И вывод будет такой.

main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
Thread-0 :1
Thread-0 :3
Thread-0 :5
Thread-0 :7
Thread-0 :9
Thread-0 :11
main  is printing
main  is printing
Thread-0 :13
Thread-0 :15
Thread-0 :17
Thread-0 :19
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing

После того, как мы запустим наш код, мы получим только вывод метода run без каких-либо параметров. Причина этого - метод start (). Метод запуска, расположенный в классе Thread, запускает только метод run, не содержащий никаких параметров.

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

Теперь давайте создадим поток, используя исполняемый интерфейс.

Runnable - это интерфейс SAM (Single Abstract Method), у него есть только один метод - метод run (). Теперь вы можете подумать, если у Runnable есть только метод run, как нам запустить наш поток. Чтобы запустить его как поток, мы создадим объект Thread, передав наш объект PrintOddnumbers в качестве параметра.

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

Примечание. Класс Thread имеет несколько разных конструкторов для создания объекта потока. Щелкните здесь, чтобы перейти к официальному документу и увидеть различные конструкторы.

Приоритет потока

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

Приоритет будет в диапазоне от 1 до 10, где 1 - самый низкий, а 10 - самый высокий приоритет.

Thread.setPriority(int i)

Задание целочисленного значения от 1 до 10 установит приоритет потока для этого потока.

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

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

Здесь мы печатаем приоритеты в родительском потоке и дочернем потоке (getPriority ()), и мы передаем целочисленное значение 9 в качестве приоритета основного потока. После этого мы создаем дочерний поток (поток OddPrinter). В этом случае система распечатает их как свои приоритеты.

Main :9
Odd Printer Thread:9

Как видно из вышесказанного, дочерний поток наследует приоритет потока от родительского потока.

Присоединиться к теме

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

Примечание. Используя метод соединения, нам нужно перехватить прерванное исключение, поскольку оно может перевести поток из состояния ожидания в рабочее состояние.

В этом случае основной поток может завершить свой блок цикла for, но как только мы вызовем метод соединения, он будет вечно ждать (состояние ожидания), пока нечетный принтер завершит свои операции. Таким образом, основной поток всегда завершается после завершения дочернего потока. Метод соединения также имеет два других перегруженных метода.

join(long milliSecond)
join(long milliSecond,int nanoSecond)

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

В заключение, чтобы поток вернулся в рабочее состояние из состояния ожидания, есть 3 возможности.

  1. Другой поток (в данном случае OddPrinter) завершает свою задачу.
  2. Время ожидания потока истекло.
  3. Поток прерван из-за метода interrupt ().

Другие методы в классе потоков

В классе потока есть и другие полезные методы, которые можно использовать для отладки.

урожай()

Метод yield () - это статический метод, который будет использоваться в потоке, вызывающем этот метод. Используя этот метод, поток сообщает планировщику JVM отдавать приоритет другим потокам над этим.

for(int i=1;i<=20;i=i++){
    if(i==10)
        Thread.yield();
 // THis thread will give importnce to other threads
 //after printing 10 numbers.
            
    System.out.println("Printer :"+i);
 }

В этом блоке кода после печати до 10 текущий поток скажет планировщику потоков отдать приоритет другим потокам над текущим. В нем не упоминается конкретный поток для определения приоритетов, который будет выбран планировщиком потоков.

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

sleep () и прерывание ()

Метод сна временно переведет поток, вызывающий этот метод, в состояние ожидания.

for(int i=1;i<=20;i++){
     if(i==10){
       try {
              Thread.sleep(10000);
              // Thread Waits for 10s before continuing
        } catch (InterruptedException ex) {
                //ExceptionHandling
        }
      }
     System.out.println("Printer :"+i);
}

В приведенном выше примере поток будет спать в течение 10 секунд (перейти в состояние ожидания) после печати 10 чисел.

Есть две возможности для выхода потока из спящего режима.

  1. Время сна истекло.
  2. Тема прервана.

Теперь давайте посмотрим на метод interput (). Метод interupt () вызывается в поток другим потоком. Если метод interupt () используется в потоке, ожидающем из-за do wait (), sleep () или join (), он перейдет из состояния ожидания обратно в состояние выполнения.

В этом примере OddPrinter будет ждать 10 секунд при запуске. Но когда мы вызываем на нем метод interupt () из main, он снова начинает работать, как видно из выходных данных ниже.

main  is printing
Odd Printer is Up
Thread-0 :1
Thread-0 :2
Thread-0 :3
Thread-0 :4
Thread-0 :5
main  is printing
main  is printing
Thread-0 :6
Thread-0 :7
Thread-0 :8
Thread-0 :9
Thread-0 :10
Thread-0 :11
Thread-0 :12
Thread-0 :13
Thread-0 :14
Thread-0 :15
Thread-0 :16
Thread-0 :17
Thread-0 :18
Thread-0 :19
Thread-0 :20
Printer thread is finished
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing
main  is printing

Примечание. Вызов interupt () в потоке, который не спит, не приведет к ошибкам, вместо этого interupt () сработает, когда поток перейдет в режим ожидания.

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

использованная литература

Видеоряд, сделанный Кришантой Динеш, является источником большей части этой статьи.