0Pricing
Java Academy · Урок

Отправка задач: Runnable и Callable

Отправляйте задачи Runnable и Callable и получайте результаты через Future.

«Отправка задач: Runnable и Callable» — бесплатный урок Java Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Задачи без результата и с результатом

Два функциональных интерфейса представляют задачи, отправляемые в ExecutorService:

  • Задача без результата: не возвращает значение и не может объявлять проверяемые исключения
  • Задача с результатом: возвращает результат типа V и может выбрасывать проверяемые исключения
import java.util.concurrent.*;

// Runnable — no return value
Runnable r = () -> System.out.println("Running");

// Callable<Integer> — returns a value
Callable<Integer> c = () -> {
    return 42; // or do computation
};

submit для задачи без результата

submit(Runnable) возвращает объект Future<?>. Вызов get() для него возвращает null, но подтверждает завершение задачи (или выбрасывает исключение, если задача завершилась с ошибкой).

ExecutorService pool = Executors.newFixedThreadPool(2);

Future<?> future = pool.submit(() ->
    System.out.println("Task done on " + Thread.currentThread().getName())
);

future.get(); // waits for completion; returns null
System.out.println("Task confirmed complete");
pool.shutdown();

submit для задачи с результатом

submit(Callable) возвращает объект Future<V>. Вызовите future.get(), чтобы получить результат (вызов блокирует выполнение до завершения задачи).

ExecutorService pool = Executors.newFixedThreadPool(2);

Callable<Integer> sumTask = () -> {
    int sum = 0;
    for (int i = 1; i <= 100; i++) sum += i;
    return sum;
};

Future<Integer> future = pool.submit(sumTask);
Integer result = future.get(); // blocks until done
System.out.println("Sum 1..100 = " + result); // 5050
pool.shutdown();

Отправка нескольких задач с результатом с помощью invokeAll

invokeAll() отправляет коллекцию задач с результатом и возвращает список объектов Future — к моменту возврата все задачи завершены:

List<Callable<String>> tasks = List.of(
    () -> "Result A",
    () -> "Result B",
    () -> "Result C"
);

ExecutorService pool = Executors.newFixedThreadPool(3);
List<Future<String>> futures = pool.invokeAll(tasks);

for (Future<String> f : futures) {
    System.out.println(f.get());
}
pool.shutdown();

invokeAny: первый успешный результат

invokeAny() отправляет коллекцию задач с результатом и возвращает результат первой успешно завершившейся задачи, отменяя остальные:

List<Callable<String>> candidates = List.of(
    () -> { Thread.sleep(200); return "Slow"; },
    () -> { Thread.sleep(50);  return "Fast"; },
    () -> { Thread.sleep(100); return "Medium"; }
);

ExecutorService pool = Executors.newFixedThreadPool(3);
String winner = pool.invokeAny(candidates);
System.out.println("First: " + winner); // Fast
pool.shutdown();

execute() и submit()

execute(Runnable) запускает задачу без отслеживания результата: объект Future не возвращается, а исключения теряются, если не добавить обработчик неперехваченных исключений. submit() оборачивает задачу в Future и сохраняет исключения, чтобы их можно было получить через get().

ExecutorService pool = Executors.newFixedThreadPool(2);

// execute: exception silently lost
pool.execute(() -> { throw new RuntimeException("Oops!"); });

// submit: exception captured in Future
Future<?> f = pool.submit(() -> { throw new RuntimeException("Oops!"); });
try {
    f.get();
} catch (ExecutionException e) {
    System.out.println("Caught: " + e.getCause().getMessage());
}
pool.shutdown();

Задача с результатом и проверяемые исключения

В отличие от задачи без результата, задача с результатом может выбрасывать проверяемые исключения, поэтому она особенно удобна для операций ввода-вывода и работы с базами данных:

Callable<String> dbQuery = () -> {
    // This compiles fine — checked exception is declared on Callable.call()
    if (Math.random() < 0.5) throw new java.sql.SQLException("DB error");
    return "row data";
};

Future<String> f = pool.submit(dbQuery);
try {
    String data = f.get();
} catch (ExecutionException e) {
    if (e.getCause() instanceof java.sql.SQLException) {
        System.out.println("DB failed: " + e.getCause().getMessage());
    }
}

Параллельное выполнение независимых вычислений

Используйте несколько отправок задач с результатом, чтобы выполнять независимые вычисления параллельно:

ExecutorService pool = Executors.newFixedThreadPool(4);

Future<Long> sumFuture  = pool.submit(() -> LongStream.rangeClosed(1,1_000_000).sum());
Future<Long> prodFuture = pool.submit(() -> LongStream.rangeClosed(1,20).reduce(1L, (a,b)->a*b));

long sum  = sumFuture.get();
long prod = prodFuture.get();
System.out.println("Sum: " + sum + ", 20!: " + prod);
pool.shutdown();

Объединение результатов задач с результатом

Отправьте множество задач и объедините результаты после завершения всех задач:

int N = 8;
ExecutorService pool = Executors.newFixedThreadPool(N);
List<Future<Integer>> futures = new ArrayList<>();

for (int i = 0; i < N; i++) {
    final int chunk = i;
    futures.add(pool.submit(() -> chunk * chunk)); // i^2
}

int total = 0;
for (Future<Integer> f : futures) total += f.get();
System.out.println("Sum of squares: " + total);
pool.shutdown();

Ограничение времени ожидания задачи с результатом

Передайте ограничение времени в future.get(timeout, unit), чтобы не ждать бесконечно:

Future<String> f = pool.submit(() -> {
    Thread.sleep(5000); // slow task
    return "done";
});
try {
    String result = f.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    System.out.println("Task timed out");
    f.cancel(true); // interrupt the task
} catch (ExecutionException e) {
    System.out.println("Task failed: " + e.getCause());
}

Рекомендации

Краткие рекомендации:

  • Используйте submit(), а не execute() — исключения сохраняются
  • Используйте задачу с результатом, когда нужно вернуть значение или обработать проверяемые исключения
  • Всегда вызывайте future.get() с ограничением времени
  • Используйте invokeAll для пакетной обработки, а invokeAny — когда нужен первый полученный ответ
  • Всегда завершайте работу пула

Быстрая проверка

В чём главное преимущество задачи с результатом перед задачей без результата?

Итоги: задачи без результата и с результатом

Основные выводы:

  • Задача без результата: не возвращает значения и не поддерживает проверяемые исключения; submit возвращает Future<?> со значением null
  • Задача с результатом: возвращает V и может выбрасывать проверяемые исключения; submit возвращает Future<V>
  • invokeAll: отправляет пакет задач и ждёт завершения всех
  • invokeAny: возвращает первый успешный результат и отменяет остальные
  • Всегда используйте submit() для сохранения исключений

Часто задаваемые вопросы

Урок «Отправка задач: Runnable и Callable» бесплатный?

Да — полный текст урока «Отправка задач: Runnable и Callable» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «Отправка задач: Runnable и Callable»?

Отправляйте задачи Runnable и Callable и получайте результаты через Future. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Отправка задач: Runnable и Callable»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. ExecutorService и типы пулов потоков
  2. Отправка задач: Runnable и Callable
  3. Future и обработка ошибок
  4. ScheduledExecutorService для повторяющихся задач
← Назад к Java Academy