Перейти к основному содержимому

Реализация Runnable и расширение потока

· 3 мин. чтения

1. Введение

«Должен ли я реализовать Runnable или расширить класс Thread »? довольно распространенный вопрос.

В этой статье мы увидим, какой подход имеет больше смысла на практике и почему.

2. Использование потока

Давайте сначала определим класс SimpleThread , который расширяет Thread :

public class SimpleThread extends Thread {

private String message;

// standard logger, constructor

@Override
public void run() {
log.info(message);
}
}

Давайте также посмотрим, как мы можем запустить поток этого типа:

@Test
public void givenAThread_whenRunIt_thenResult()
throws Exception {

Thread thread = new SimpleThread(
"SimpleThread executed using Thread");
thread.start();
thread.join();
}

Мы также можем использовать ExecutorService для выполнения потока:

@Test
public void givenAThread_whenSubmitToES_thenResult()
throws Exception {

executorService.submit(new SimpleThread(
"SimpleThread executed using ExecutorService")).get();
}

Это довольно много кода для запуска одной операции журнала в отдельном потоке.

Также обратите внимание, что SimpleThread не может расширять какой-либо другой класс , поскольку Java не поддерживает множественное наследование.

3. Реализация Runnable

Теперь давайте создадим простую задачу, реализующую интерфейс java.lang.Runnable :

class SimpleRunnable implements Runnable {

private String message;

// standard logger, constructor

@Override
public void run() {
log.info(message);
}
}

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

Существуют различные подходы, которые мы можем использовать для его запуска; один из них — использовать класс Thread :

@Test
public void givenRunnable_whenRunIt_thenResult()
throws Exception {
Thread thread = new Thread(new SimpleRunnable(
"SimpleRunnable executed using Thread"));
thread.start();
thread.join();
}

Мы даже можем использовать ExecutorService :

@Test
public void givenARunnable_whenSubmitToES_thenResult()
throws Exception {

executorService.submit(new SimpleRunnable(
"SimpleRunnable executed using ExecutorService")).get();
}

Подробнее об ExecutorService можно прочитать здесь .

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

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

Мы можем переписать приведенный выше код Runnable , используя лямбда-выражение :

@Test
public void givenARunnableLambda_whenSubmitToES_thenResult()
throws Exception {

executorService.submit(
() -> log.info("Lambda runnable executed!"));
}

4. Работающий или поток ?

Проще говоря, мы обычно поощряем использование Runnable поверх Thread :

  • При расширении класса Thread мы не переопределяем ни один из его методов. Вместо этого мы переопределяем метод Runnable ( который реализует Thread ) . Это явное нарушение принципа IS-A Thread .
  • Создание реализации Runnable и передача ее классу Thread использует композицию, а не наследование, что более гибко .
  • После расширения класса Thread мы не можем расширить какой-либо другой класс .
  • Начиная с Java 8, Runnables могут быть представлены в виде лямбда-выражений.

5. Вывод

В этом кратком руководстве мы увидели, что реализация Runnable обычно является лучшим подходом, чем расширение класса Thread .

Код для этого поста можно найти на GitHub .