ExecutorService.submit: примеры (JAVA)
ExecutorService.submit(Callable task): Future Описание метода ExecutorService.submit()
Метод ExecutorService.submit() служит для отправки задачи на выполнение в пул потоков. Он существует в нескольких перегрузках и возвращает объект Future<T>, который представляет собой результат выполнения задачи и позволяет получить значение или статус задачи асинхронно.
Основные перегрузки:
- Future<T> submit(Callable<T> task) - принимает Callable, выполняет его и возвращает Future с результатом типа T. Исключения, выброшенные в Callable, при вызове get() будут обернуты в ExecutionException.
- Future<?> submit(Runnable task) - принимает Runnable, возвращает Future<?>. Результирующее значение при вызове get() будет null (если не передано иное).
- <T> Future<T> submit(Runnable task, T result) - принимает Runnable и заранее заданный результат типа T, который возвращается при вызове get() после завершения задачи.
Поведение и важные замечания:
- Вызов submit помещает задачу в пул и возвращает Future немедленно; выполнение может быть отложено в зависимости от конфигурации пула.
- Чтобы получить результат, требуется вызвать future.get(). Этот вызов блокирует текущий поток, пока результат не станет доступен, или до тех пор, пока не будет выброшено исключение (InterruptedException или ExecutionException).
- Future поддерживает методы cancel(), isDone(), isCancelled(). Отмена задачи может быть принудительной, если передан флаг прерывания.
- Если пул завершает работу (shutdown) и новые задачи не принимаются, вызов submit приведет к выбрасыванию RejectedExecutionException в момент отправки.
- При использовании с ForkJoinPool некоторые задаче оформляются как ForkJoinTask, но семантика submit остаётся прежней - возвращается Future.
Аргументы и возвращаемые значения подробно:
- Аргумент: Callable<T> - функциональный интерфейс, метод call() возвращает T и может выбрасывать исключение.
- Аргумент: Runnable - функциональный интерфейс, метод run() ничего не возвращает и не допускает проверяемых исключений; если нужно вернуть значение, используется перегрузка с параметром result.
- Возвращаемое значение: Future<T> - предоставляет доступ к результату, к статусу и методам отмены. Значение T доступно через get(), либо null при submit(Runnable).
Ключевые сценарии использования: запуск задач с результатом, накопление результатов параллельно, синхронизация потоков через получение результата, управление отменой и таймаутами через API Future.
Короткие примеры использования
Пример 1 - submit с Callable, получение результата:
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> f = es.submit(() -> {
Thread.sleep(100);
return 42;
});
System.out.println(f.get());
es.shutdown();
42
Пример 2 - submit с Runnable без результата:
ExecutorService es = Executors.newSingleThreadExecutor();
Future<?> f = es.submit(() -> System.out.println("Hello from runnable"));
f.get(); // дождаться завершения
es.shutdown();
Hello from runnable (null)
Пример 3 - submit с Runnable и заранее заданным результатом:
ExecutorService es = Executors.newCachedThreadPool();
Future<String> f = es.submit(() -> System.out.println("Task executed"), "OK");
System.out.println(f.get());
es.shutdown();
Task executed OK
Пример 4 - отмена задачи через Future:
ExecutorService es = Executors.newFixedThreadPool(1);
Future<?> f = es.submit(() -> {
try { Thread.sleep(5000); } catch (InterruptedException e) { System.out.println("Interrupted"); }
});
Thread.sleep(100);
f.cancel(true);
System.out.println("isCancelled=" + f.isCancelled() + ", isDone=" + f.isDone());
es.shutdownNow();
Interrupted isCancelled=true, isDone=true
Похожие API в Java и их особенности
- execute(Runnable) (интерфейс Executor) - принимает Runnable и не возвращает Future. Подходит, когда результат и управление задачей не требуются.
- invokeAll(Collection<Callable<T>>) - отправляет набор Callable и блокирует до получения всех результатов. Удобно для батчевых операций, когда требуется дождаться каждого результата.
- invokeAny(Collection<Callable<T>>) - возвращает результат первой успешно завершившейся задачи, остальные могут быть отменены. Полезно при параллельном поиске первого успешного ответа.
- CompletableFuture.supplyAsync() - высокоуровневый API для композиции асинхронных операций, цепочек и обработки исключений, более удобен для сложной логики, чем низкоуровневые Future.
- ForkJoinPool.invoke() - эффективен для рекурсивных задач, использующих алгоритм 'разделяй и властвуй'. Для таких задач ForkJoinPool часто предпочтительнее стандартного ThreadPoolExecutor.
Выбор зависит от целей: если требуется только запуск задачи без результата, выбрать execute; для получения всех результатов подряд - invokeAll; для более гибкой асинхронной композиции - CompletableFuture.
Аналоги в других языках и примеры
Краткое сравнение и примеры кода с результатом.
Python - concurrent.futures.ThreadPoolExecutor.submit:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=2) as ex:
f = ex.submit(lambda: 7 * 6)
print(f.result())
42
JavaScript - Promise и worker threads (на сервере). Пример с Promise и setTimeout:
const task = () => new Promise(res => setTimeout(() => res(42), 100));
task().then(console.log);
42
C# - Task.Run (асинхронный запуск):
using System;
using System.Threading.Tasks;
var t = Task.Run(() => 42);
Console.WriteLine(t.Result);
42
Go - goroutine и канал для получения результата:
package main
import "fmt"
func main(){
ch := make(chan int)
go func(){ ch <- 42 }()
fmt.Println(<-ch)
}
42
Kotlin - корутины (аналог асинхронного выполнения):
import kotlinx.coroutines.*
fun main() = runBlocking {
val d = async { 6 * 7 }
println(d.await())
}
42
PHP - параллельные расширения, пример с параллель (PECL) или с pthreads (в CLI):
// пример с параллельным расширением
$runtime = new parallel\Runtime();
$future = $runtime->run(function(){ return 6*7; });
echo $future->value();
42
Lua - корутины не используют системные потоки; для реального параллелизма требуются внешние библиотеки (luaproc и т.п.). Простой пример с coroutine:
co = coroutine.create(function() coroutine.yield(42) end)
coroutine.resume(co)
print(coroutine.resume(co)) -- дважды для показа
true 42 true nil
SQL - в классическом SQL нет аналога; базы данных поддерживают фоновые задания и планировщики (например, pg_cron в PostgreSQL), но модель отлична от ExecutorService.
Основные отличия от Java ExecutorService:
- Нативные механизмы в других языках часто менее формализованы и зависят от фреймворка или библиотеки.
- Go и Kotlin предлагают лёгкие конкурентные примитивы (goroutine, coroutine), которые более легковесны по сравнению с потоками JVM.
- JavaScript использует event-loop и промисы, что отличается от многопоточности; Worker threads дает более близкую модель, но менее распространена.
Типичные ошибки при использовании
- Ожидание результата без обработки исключений: при вызове get() возможно получение ExecutionException или InterruptedException. Пример:
ExecutorService es = Executors.newSingleThreadExecutor();
Future<Integer> f = es.submit(() -> { throw new RuntimeException("fail"); });
try { System.out.println(f.get()); } catch(Exception e) { e.printStackTrace(); }
es.shutdown();
java.util.concurrent.ExecutionException: java.lang.RuntimeException: fail
at ...
Caused by: java.lang.RuntimeException: fail
at ...
- Не выполнение shutdown пула - приводит к утечкам потоков и завершению процесса, ожидающему завершения. Частая ошибка в тестах и демо-коде.
- Неправильная отмена: при вызове cancel(false) задача не будет прервана, если она заблокирована; ожидание прерывания требует cancel(true) и корректной обработки InterruptedException внутри задачи.
- Submit null - вызов submit(null) приводит к NullPointerException при попытке отправки.
- Непонимание семантики submit(Runnable) - результат get() может быть null и не указывать на полезное значение.
- Вызов get() без таймаута в критическом потоке UI или сетевом потоке, приводящий к блокировке интерфейса или зависанию сервиса.
Изменения и эволюция API
Интерфейс ExecutorService и метод submit() существуют с Java 5 и остаются стабильными. Последние ключевые изменения в экосистеме конкурентности JVM:
- Появление CompletableFuture (Java 8) - более мощный инструмент для композиции асинхронных операций, дополняющий классический Future.
- Развитие ForkJoinPool и поддержка параллельных стримов - альтернативные модели выполнения задач.
- Проект Loom и виртуальные потоки (preview/GA в более поздних версиях JDK) привнесли фабрики потоков, например Executors.newVirtualThreadPerTaskExecutor, что влияет на выбор реализаций ExecutorService для высокопараллельных задач, но метод submit сам по себе не изменился.
Расширенные и редкие сценарии применения
1) Обработка результатов по мере завершения задач с помощью ExecutorCompletionService:
ExecutorService es = Executors.newFixedThreadPool(3);
CompletionService<String> cs = new ExecutorCompletionService<>(es);
cs.submit(() -> { Thread.sleep(300); return "A"; });
cs.submit(() -> { Thread.sleep(100); return "B"; });
cs.submit(() -> { Thread.sleep(200); return "C"; });
for (int i = 0; i < 3; i++) {
Future<String> f = cs.take(); // блокирует до следующего завершения
System.out.println(f.get());
}
es.shutdown();
B C A
2) Инициализация ThreadPoolExecutor с кастомной стратегией отклонения и отправка задач через submit:
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
1, 2, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 5; i++) {
final int id = i;
tpe.submit(() -> {
System.out.println("Task " + id + " running");
Thread.sleep(200);
return null;
});
}
tpe.shutdown();
Task 0 running Task 1 running Task 2 running Task 3 running Task 4 running
Пояснение: при переполнении очереди и занятии всех потоков политика CallerRunsPolicy заставляет вызывающий поток выполнить задачу, что снижает скорость отправки и предотвращает отказ.
3) Комбинация submit и CompletableFuture для асинхронной композиции:
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> f = es.submit(() -> 21);
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
try { return f.get(); } catch (Exception e) { throw new RuntimeException(e); }
}, es).thenApply(x -> x * 2);
System.out.println(cf.join());
es.shutdown();
42
4) Submit на виртуальных потоках (при наличии поддержки в JVM) - полезно для большого числа коротких блокирующих задач:
try (ExecutorService es = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> f = es.submit(() -> {
Thread.sleep(50);
return "ok";
});
System.out.println(f.get());
}
ok
5) Обработка таймаутов и отключений через get с таймаутом и последующей отменой:
ExecutorService es = Executors.newSingleThreadExecutor();
Future<String> f = es.submit(() -> { Thread.sleep(5000); return "done"; });
try {
System.out.println(f.get(200, TimeUnit.MILLISECONDS));
} catch (TimeoutException te) {
f.cancel(true);
System.out.println("timed out and cancelled");
}
es.shutdownNow();
timed out and cancelled
6) Submit внутри задачи для создания цепочек задач - осторожно с блокировками пула (deadlock), если пул фиксирован и все потоки заняты ожиданием результатов дочерних задач.
7) Ловля ExecutionException и извлечение причины для подробной диагностики:
Future<Integer> f = es.submit(() -> { throw new IllegalStateException("boom"); });
try { f.get(); } catch (ExecutionException ee) { System.out.println(ee.getCause().getMessage()); }
boom
Описанные расширенные примеры показывают, как применять submit для тонкой настройки поведения, обработки результатов по мере доступности, обеспечения отказоустойчивости и интеграции с современными асинхронными абстракциями JVM.
джава ExecutorService.submit function comments
- джава ExecutorService.submit - аргументы и возвращаемое значение
- Функция java ExecutorService.submit - описание
- ExecutorService.submit - примеры
- ExecutorService.submit - похожие методы на java
- ExecutorService.submit на javascript, c#, python, php
- ExecutorService.submit изменения java
- Примеры ExecutorService.submit на джава