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

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

1. Вступление

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

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

2. ИспользуяThread

Давайте сначала определим класс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 cannot extend any other class, поскольку 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 вhere.

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

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

We can rewrite the above Runnable code using a lambda expression:

@Test
public void givenARunnableLambda_whenSubmitToES_thenResult()
  throws Exception {

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

4. Runnable илиThread?

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

  • При расширении классаThread мы не переопределяем ни один из его методов. Вместо этого мы переопределяем методRunnable (w, которыйThread реализует). Это явное нарушение принципа IS-AThread.

  • Создание реализацииRunnable и передача ее классуThread использует композицию, а не наследование, что является более гибким

  • После расширения классаThread мы не сможем расширить любой другой класс.

  • Начиная с Java 8,Runnables можно представить как лямбда-выражения.

5. Заключение

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

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