В чём заключается различие между методами 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 с логированием.