← Назад к вопросам

Что возвращает submit в ThreadPoolExecutor?

2.7 Senior🔥 21 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

ThreadPoolExecutor.submit(): Возвращаемое Значение и Использование

Этот вопрос проверяет понимание конкурентности в Java и взаимодействия с потоками. Это критически важно для написания надежного многопоточного кода.

Краткий Ответ

submit() возвращает Future<?> объект, который позволяет:

  • Получить результат выполнения задачи
  • Дождаться завершения
  • Отменить задачу
  • Обработать исключения

Подробное Объяснение

Сигнатуры submit()

public interface ExecutorService extends Executor {
    
    // Возвращает Future без результата (void)
    <T> Future<T> submit(Callable<T> task);
    
    // Возвращает Future с результатом
    Future<?> submit(Runnable task);
    
    // Возвращает Future с конкретным результатом
    <T> Future<T> submit(Runnable task, T result);
}

// ThreadPoolExecutor реализует ExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService { ... }

Три Варианта submit()

Вариант 1: submit(Callable)

ExecutorService executor = Executors.newFixedThreadPool(2);

// Callable с возвращаемым значением
Future<Integer> future = executor.submit(() -> {
    System.out.println("Doing expensive computation...");
    return 42; // Возвращаем результат
});

try {
    // Ждем результата (блокирующий вызов)
    Integer result = future.get(); // Будет 42
    System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

Вариант 2: submit(Runnable)

Future<?> future = executor.submit(() -> {
    System.out.println("Task executed");
    // Runnable не возвращает значение
});

try {
    // get() вернет null для Runnable
    future.get(); // Ждем завершения
    System.out.println("Task completed");
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

Вариант 3: submit(Runnable, Result)

Future<String> future = executor.submit(
    () -> {
        System.out.println("Doing work...");
        // Не возвращаем значение
    },
    "Operation completed" // Результат, если успешно
);

try {
    String result = future.get(); // Вернет "Operation completed"
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

Future: Что Можно Делать

Future<Integer> future = executor.submit(() -> {
    Thread.sleep(2000); // Имитируем долгую операцию
    return 42;
});

// 1. Проверить, завершена ли задача
boolean isDone = future.isDone(); // false (еще выполняется)

// 2. Отменить задачу
boolean cancelled = future.cancel(true); // true (если успешно отменена)
// true = прервать, если выполняется
// false = отменить только если не начала выполняться

// 3. Проверить, отменена ли
boolean isCancelled = future.isCancelled(); // true (если отменена)

// 4. Получить результат (блокирует текущий поток)
try {
    Integer result = future.get(); // Ждет максимум 2 сек
} catch (ExecutionException e) {
    // Исключение из задачи
} catch (InterruptedException e) {
    // Текущий поток прерван
}

// 5. Получить результат с timeout
try {
    Integer result = future.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    // Истек timeout
    System.out.println("Task took too long");
}

Практический Пример: Обработка Результатов

public class DataProcessor {
    private final ExecutorService executor = 
        Executors.newFixedThreadPool(4);
    
    // ❌ НЕПРАВИЛЬНО: Игнорируем Future
    public void badApproach() {
        executor.submit(() -> {
            // Исключение? Мы не узнаем
            // Завершилась ли задача? Не знаем
            processData();
        });
        // "set and forget"
    }
    
    // ✅ ПРАВИЛЬНО: Обрабатываем Future
    public void goodApproach() throws ExecutionException, InterruptedException {
        Future<String> future = executor.submit(() -> {
            return processData();
        });
        
        try {
            String result = future.get(); // Получаем результат
            logger.info("Data processed: " + result);
        } catch (ExecutionException e) {
            // Исключение внутри задачи
            logger.error("Processing failed", e.getCause());
        }
    }
    
    private String processData() {
        // может выбросить исключение
        return "processed";
    }
}

Обработка Множества Future

Вариант 1: invokeAll() — жди все

public void processMultipleTasks() throws InterruptedException {
    List<Callable<Integer>> tasks = Arrays.asList(
        () -> { Thread.sleep(1000); return 1; },
        () -> { Thread.sleep(2000); return 2; },
        () -> { Thread.sleep(3000); return 3; }
    );
    
    // Ждет завершения ВСЕх задач
    List<Future<Integer>> futures = executor.invokeAll(tasks);
    
    for (Future<Integer> future : futures) {
        try {
            Integer result = future.get(); // Уже готово, не блокирует
            System.out.println("Result: " + result);
        } catch (ExecutionException e) {
            logger.error("Task failed", e.getCause());
        }
    }
}

Вариант 2: invokeAny() — жди первую

public void processFirstCompleted() throws ExecutionException, InterruptedException {
    List<Callable<String>> tasks = Arrays.asList(
        () -> { Thread.sleep(5000); return "slow"; },
        () -> { Thread.sleep(1000); return "fast"; },
        () -> { Thread.sleep(3000); return "medium"; }
    );
    
    // Вернет результат ПЕРВОЙ завершенной задачи
    String result = executor.invokeAny(tasks); // "fast"
    System.out.println("Got result: " + result);
    // Остальные задачи отмен
}

Вариант 3: Ручной loop

public void manualProcessing() throws InterruptedException, ExecutionException {
    List<Future<String>> futures = new ArrayList<>();
    
    // Запускаем задачи
    for (int i = 0; i < 10; i++) {
        futures.add(executor.submit(() -> processItem(i)));
    }
    
    // Обрабатываем по мере готовности
    ExecutorCompletionService<String> completionService = 
        new ExecutorCompletionService<>(executor);
    
    // Или обрабатываем в порядке завершения
    for (int i = 0; i < futures.size(); i++) {
        Future<String> future = completionService.take(); // Ждет готовности
        String result = future.get();
        System.out.println("Processed: " + result);
    }
}

Важно: Exception Handling

Future<Integer> future = executor.submit(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("Random error!");
    }
    return 42;
});

try {
    Integer result = future.get();
    System.out.println("Success: " + result);
} catch (ExecutionException e) {
    // ✅ Исключение из Callable/Runnable
    Throwable cause = e.getCause(); // Оригинальное исключение
    logger.error("Task threw exception: ", cause);
} catch (InterruptedException e) {
    // ❌ Текущий поток прерван
    logger.error("Waiting was interrupted", e);
    // Хорошая практика: отменить задачу
    future.cancel(true);
}

Реальный Пример: Обработка HTTP Запросов

@Service
public class ApiService {
    private final ExecutorService executor = 
        Executors.newFixedThreadPool(10);
    
    public List<UserData> fetchMultipleUsers(List<String> userIds) 
            throws InterruptedException, ExecutionException {
        
        // Запускаем запросы параллельно
        List<Future<UserData>> futures = userIds.stream()
            .map(id -> executor.submit(() -> fetchUserFromApi(id)))
            .collect(Collectors.toList());
        
        // Ждем всех
        List<UserData> results = new ArrayList<>();
        for (Future<UserData> future : futures) {
            try {
                results.add(future.get(5, TimeUnit.SECONDS));
            } catch (TimeoutException e) {
                logger.warn("Request timeout, cancelling");
                future.cancel(true);
            } catch (ExecutionException e) {
                logger.error("Failed to fetch user", e.getCause());
                // Можем пропустить ошибку или обработать
            }
        }
        
        return results;
    }
    
    private UserData fetchUserFromApi(String userId) {
        // HTTP запрос
        return httpClient.get("/users/" + userId);
    }
}

Распространённые Ошибки

// ❌ Ошибка 1: Игнор Future
executor.submit(() -> {
    criticalOperation();
});
// Если exception — узнаем слишком поздно

// ✅ Исправление
Future<?> future = executor.submit(() -> {
    criticalOperation();
});

// ❌ Ошибка 2: Забыли get() с timeout
future.get(); // Может зависнуть навсегда

// ✅ Исправление  
future.get(5, TimeUnit.SECONDS); // С timeout

// ❌ Ошибка 3: Неправильная обработка исключений
try {
    future.get();
} catch (Exception e) {
    // Слишком общий catch
}

// ✅ Исправление
try {
    future.get();
} catch (ExecutionException e) {
    // Исключение в задаче
    logger.error("Task error", e.getCause());
} catch (InterruptedException e) {
    // Прерывание потока
    future.cancel(true);
}

Заключение

submit() возвращает Future<T>, который:

  1. Позволяет получить результат: future.get()
  2. Позволяет дождаться завершения: future.get() блокирует
  3. Позволяет отменить задачу: future.cancel(true)
  4. Обрабатывает исключения: через ExecutionException
  5. Поддерживает timeout: future.get(timeout, unit)

Важно помнить:

  • Всегда обрабатывай Future, не игнорируй
  • Используй try-with-resources для executor'а
  • Всегда лови ExecutionException и InterruptedException
  • Используй get(timeout, unit) чтобы избежать зависания
  • Future — ключ к надежному многопоточному коду