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-AThread .
- Создание реализации
Runnable
и передача ее классуThread
использует композицию, а не наследование, что более гибко . - После расширения класса
Thread
мы не можем расширить какой-либо другой класс . - Начиная с Java 8,
Runnables
могут быть представлены в виде лямбда-выражений.
5. Вывод
В этом кратком руководстве мы увидели, что реализация Runnable
обычно является лучшим подходом, чем расширение класса Thread .
Код для этого поста можно найти на GitHub .