Реализация 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.