Gymterview
middle

В чём заключается различие между методами submit() и execute() у пула потоков?

Оба метода используются для подачи задач в пул потоков, но различаются по интерфейсу, возвращаемому значению и обработке исключений.

Сравнение

Характеристика execute(Runnable) submit(...)
Определён в Executor ExecutorService
Принимает Только Runnable Runnable, Callable<V>, Runnable + result
Возвращает void Future<V>
Обработка исключений Непойманное исключение вызывает UncaughtExceptionHandler; поток завершается и заменяется новым Исключение сохраняется в Future; при вызове get() выбрасывается ExecutionException

Сигнатуры

Пример
// Executor
void execute(Runnable command);

// ExecutorService
Future<?> submit(Runnable task);
Future<V> submit(Callable<V> task);
Future<V> submit(Runnable task, V result);

Критическое различие: обработка исключений

Это самое важное практическое различие:

Пример: разная обработка исключений
ExecutorService executor = Executors.newSingleThreadExecutor();

// execute() — исключение «видно» в консоли
executor.execute(() -> {
    throw new RuntimeException("Ошибка в execute()");
    // Стек-трейс напечатается в System.err
    // Поток пула завершится и будет заменён новым
});

// submit() — исключение «прячется» в Future
Future<?> future = executor.submit(() -> {
    throw new RuntimeException("Ошибка в submit()");
    // Ничего не напечатается! Исключение «спрятано»
});

try {
    future.get(); // ExecutionException с причиной RuntimeException
} catch (ExecutionException e) {
    System.err.println("Поймали: " + e.getCause().getMessage());
}

executor.shutdown();

Когда что использовать

Сценарий Рекомендация
«Забыл и забил» (fire-and-forget) execute() – проще, исключения видны сразу
Нужен результат вычисления submit(Callable) – получите результат через Future.get()
Нужно отменить задачу submit() – вернёт Future, у которого есть cancel()
Нужно дождаться завершения submit()Future.get() блокирует до завершения

Опасность submit() без get()

Пример
// ОПАСНО: исключение потеряно навсегда!
executor.submit(() -> {
    processPayment(); // Если тут исключение — никто не узнает
});

// БЕЗОПАСНО: исключение будет обработано
Future<?> future = executor.submit(() -> processPayment());
future.get(); // Выбросит ExecutionException если processPayment() упал

Аналогия из жизни. execute() – вы отправили письмо обычной почтой: если что-то пошло не так, вам пришлют уведомление о недоставке. submit() – вы отправили письмо с уведомлением о вручении (Future): вы точно узнаете результат, но только если проверите уведомление (get()). Если не проверите – не узнаете ни об успехе, ни об ошибке.

На собеседовании. Основной акцент на обработке исключений: (1) execute() – исключение передаётся в UncaughtExceptionHandler, видно в логах; (2) submit() – исключение «прячется» в Future, и если не вызвать get(), баг останется незамеченным. Это частый источник скрытых ошибок в production. Рекомендация: если используете submit() без get(), оберните задачу в try-catch с логированием.