Чем различаются thread и runnable
Перейти к содержимому

Чем различаются thread и runnable

  • автор:

В чем разница между Thread и Runnable?

В чем разница между Thread и Thread (Runnable)? Иными словами, какой плюс от того, что поток Thread будет реализован через интерфейс Runnable? Это типа поток в потоке или какие-то доп. функции будут? К примеру, доступ к UI.

Отслеживать
11 1 1 золотой знак 2 2 серебряных знака 8 8 бронзовых знаков
задан 16 фев 2015 в 18:56
407 1 1 золотой знак 4 4 серебряных знака 12 12 бронзовых знаков
Runnable это просто интерфейс, который описывает класс с методом, он потоков не создает.
11 июл 2019 в 16:00

6 ответов 6

Сортировка: Сброс на вариант по умолчанию

  1. Многопоточность в JAVA отнюдь не ограничена классом Thread
  2. В контексте определённой задачи может быть выгоднее наследовать какой-то другой класс, но множественное наследование в JAVA не поддерживается, выход: implements Runnable
  3. Интерфейс Runnable имеет посредственное отношение к потокам — его следует расценивать как передаваемую функцию, которая может быть выполнена где-то в другом месте (поток, очередь, класс, метод и т.п.)

Отслеживать
ответ дан 16 фев 2015 в 20:39
12.1k 1 1 золотой знак 15 15 серебряных знаков 31 31 бронзовый знак

Thread — это абстракция над физическим потоком.

Runnable — это абстракция над выполняемой задачей.

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

Отслеживать
ответ дан 16 фев 2015 в 18:58
13.1k 1 1 золотой знак 28 28 серебряных знаков 28 28 бронзовых знаков

Runnable не фигня, а интерфейс. А интерфейс для наследования. Похоже, вам никогда не приходилось иметь дело с ромбовидным наследованием.

16 фев 2015 в 19:31

@Futurama ну про тред вы поняли, значит язык человеческий. Runnable — это не фигня, а задача, которая может выполняться в потоке (в текущем или специально для этого созданном). Очевидно, что один поток может выполнять последовательно множество задач. А раз так, то нам нужно уметь отделять одно от другого, чтобы можно было этим управлять. Примеров на просторах сети можете нарыть множество.

16 фев 2015 в 19:32
@Futurama, ну-ка приведите пример выполнения задачи в Thread без использования Runnable .
16 фев 2015 в 19:51

@Futurama посудите сами, если мы знаем, что у нас есть тред и с ним уже ассоциирована задача, то было бы удобно иметь способ запуска задачи в этом треде. Ну конечно же тред не может выполнять задачи без Runnable в силу реализации (Thread implements Runnable) 🙂

16 фев 2015 в 19:53
@Futurama вы сами понимаете, что это код делает? Отвечаю: реализует интерфейс Runnable 🙂
16 фев 2015 в 19:54

@Futurama, в документации совершенно четко прописан ответ на Ваш вопрос.

Читаем (жирный шрифт — это выделено мной):

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:

 class PrimeThread extends Thread < long minPrime; PrimeThread(long minPrime) < this.minPrime = minPrime; >public void run() < // compute primes larger than minPrime . . . >> 

The following code would then create a thread and start it running:

 PrimeThread p = new PrimeThread(143); p.start(); 

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

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following:

 class PrimeRun implements Runnable < long minPrime; PrimeRun(long minPrime) < this.minPrime = minPrime; >public void run() < // compute primes larger than minPrime . . . >> 

The following code would then create a thread and start it running:

 PrimeRun p = new PrimeRun(143); new Thread(p).start(); 

В принципе, как говориться — «те же яйца, вид сбоку».

Если вдуматься, то суть та же. Создается (подготавливается к запуску) новый поток (класс Thread — структура данных внутри JVM). В эту структуру заносится адрес функции (последовательности инструкций JVM), которая представляет программу, которую мы хотим выполнять в новом потоке. Вызов метода .start класса Thread (непосредственно или опосредовано, как унаследованного в первом случае) запустит новый поток (как именно — зависит от ОС в которой работает конкретная JVM).

Вот собственно и все. Просто RTFM.

Runnable и Thread

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

Та часть кода, которая должна выполняться в отдельном потоке, выносится в свой класс, имеющий переопределенный метод run(). Код метода run() выполняется, когда к объекту типа Thread применяется метод start(). Непосредственный вызов run() новый поток не создает.

public class ThreadTest  public static void main(String[] args) throws InterruptedException  AnotherRun anotherRun = new AnotherRun(); Thread childTread = new Thread(anotherRun); childTread.start(); for (int i = 0; i  3; i++)  System.out.println("m" + i); Thread.sleep(1000); > childTread.join(); System.out.println("End"); > > class AnotherRun implements Runnable  @Override public void run()  for (int i = 0; i  5; i++)  System.out.println("r" + i); try  Thread.sleep(1000); > catch (InterruptedException e)  System.out.println("Interrupt"); > > > >
m0 r0 m1 r1 m2 r2 r3 r4 End

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

Метод join() заставляет текущий поток ждать завершения нити, к которой применяется. Только после этого текущий поток может продолжить выполнение своего кода.

В данном случае мы создаем класс-наследник от Runnable. Объект типа Runnable или его производное передается в конструктор объекта типа Thread. После этого поток запускается.

Другой вариант – когда пользовательский класс является наследником Thread:

public class ThreadTest1  public static void main(String[] args) throws InterruptedException  AnotherTask thread = new AnotherTask(); thread.start(); for (int i = 0; i  3; i++)  System.out.println("m" + i); Thread.sleep(1000); > thread.join(); System.out.println("End"); > > class AnotherTask extends Thread  @Override public void run() for (int i = 0; i  5; i++)  System.out.println("r" + i); try  Thread.sleep(1000); > catch (InterruptedException e)  System.out.println("Interrupt"); > > > >

Этот вариант не подходит, если класс для организации отдельного потока должен наследоваться от другого класса (не Thread). Поскольку в Java нет множественного наследования классов, приходится использовать наследование от интерфейса Runnable. Также данный подход не дает возможности запускать несколько потоков на основе одного объекта. Так в первом примере мы могли бы передать единственный объект anotherRun в несколько объектов типа Thread.

Напомним, библиотечный класс Thread сам является наследником Runnable.

Если в отдельный поток обособляется небольшая подзадача, можно использовать неименованный класс:

public class ThreadNoName  public static void main(String[] args)  Thread thread = new Thread(new Runnable()  @Override public void run()  System.out.println("hi"); > >); thread.start(); Thread thread1 = new Thread()  @Override public void run()  System.out.println("hello"); > >; thread1.start(); > >

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

Для прерывания выполнения нити, если это необходимо, используется метод interrupt(), который устанавливает переменную isInterrupt в значение true. К коде пользовательского класса, унаследованного от Runnable/Thread, это переменная должна проверяться. Отсюда следует, что на самом деле в Java нет возможности прервать поток извне, поток может остановиться только сам.

С другой стороны, в метод sleep() уже встроена проверка переменной isInterrupt, поэтому проверку вручную опускают. Если sleep() считывает наличие прерывания, то генерирует исключение.

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class ThreadInterrupt  public static void main(String[] args) throws IOException  Thread thread = new InterruptedClass(); thread.start(); BufferedReader reader = new BufferedReader( new InputStreamReader(System.in)); reader.readLine(); thread.interrupt(); > > class InterruptedClass extends Thread  @Override public void run()  while (true)  System.out.print("a"); try  sleep(1000); > catch (InterruptedException e)  break; > > > >

В примере основной поток ожидает ввод данных, в это время выполняется вторая нить. Но как только вы нажмете Enter, выполнится метод interrupt(). В свою очередь метод sleep() прочитает значение переменной isInterrupt класса Thread и сгенерирует исключение InterruptedException.

Если sleep() не используется, то isInterrupt проверяется вручную методом isInterrupted(). Следующий пример содержит ошибку, приводящую к зацикливанию:

public class ThreadInterrupt2  static int a = 1; public static void main(String[] args) throws InterruptedException  Thread thread = new WithoutSleep(); thread.start(); Thread.sleep(2000); thread.interrupt(); > > class WithoutSleep extends Thread  @Override public void run()  while (true)  System.out.println("hi"); > > >

Мы могли бы ожидать, что через 2 секунды сработает метод interrupt(), который прервет дочернюю нить. Однако, поскольку в ней не проверяется значение isInterrupt, цикл продолжает работать. Корректный код может выглядеть так:

class WithoutSleep extends Thread  @Override public void run()  while (!this.isInterrupted())  System.out.println("hi"); > > >

При наследовании от Runnable текущий поток через this получить нельзя. Его получают, вызывая соответствующий метод класса Thread:

class WithoutSleep2 implements Runnable  @Override public void run()  Thread cur = Thread.currentThread(); while (!cur.isInterrupted())  System.out.println("hi"); > > >

X Скрыть Наверх

Программирование на Java. Курс

Разница между инструментами Runnable и extends Thread в Java

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

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

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

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

Разделение задачи как Runnable означает, что мы можем повторно использовать задачу, а также иметь свободу ее выполнения из разных средств, так как вы не можете перезапустить Thread после его завершения. Таким образом, снова «реализует Runnable» и «extends Thread» для задачи, реализация Runnable будет лучшим выбором.

Каким образом можно создать поток?

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

Чем различаются Thread и Runnable?

Если мы говорим про интерфейс Runnable, то мы определяем метод run, в теле метода определяем идею того, для чего этот поток создан, потом создаем новый экземпляр класса Thread, и в качестве параметра передаем тот класс, который был реализован от интерфейса Runnable. И после этого мы вызываем метод start, который запускает на выполнение этот поток.

Если мы говорим про реализацию класса Thread, то мы переопределяем реализованный метод run, и после этого создаём новый объект класса и запускаем на выполнение поток методом start.

В чём заключается разница между методами start() и run()?

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

Как принудительно запустить поток?

Никак. В Java не существует абсолютно никакого способа принудительного запуска потока. Это контролируется JVM и Java не предоставляет никакго API для управления этим процессом.

Что такое «монитор» в Java?

Монитор, мьютекс (mutex) – это средство обеспечения контроля за доступом к ресурсу. У монитора может быть максимум один владелец в каждый текущий момент времени. Следовательно, если кто-то использует ресурс и захватил монитор для обеспечения единоличного доступа, то другой, желающий использовать тот же ресурс, должен подождать освобождения монитора, захватить его и только потом начать использовать ресурс.

Удобно представлять монитор как id захватившего его объекта. Если этот id равен 0 – ресурс свободен. Если не 0 – ресурс занят. Можно встать в очередь и ждать его освобождения.

В Java у каждого экземпляра объекта есть монитор, который контролируется непосредственно виртуальной машиной. Используется он так: любой нестатический synchronized-метод при своем вызове прежде всего пытается захватить монитор того объекта, у которого он вызван (на который он может сослаться как на this). Если это удалось – метод исполняется. Если нет – поток останавливается и ждет, пока монитор будет отпущен.

Дайте определение понятию «синхронизация».

Синхронизация это процесс, который позволяет выполнять потоки параллельно.

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

Добавить комментарий

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