Какие интерфейсы в Java используются для создания задач, выполняемых в потоках?

Ответ

Основные интерфейсы — Runnable и Callable<V>.

1. Интерфейс Runnable

  • Определяет задачу без возвращаемого значения и без возможности проброса проверяемых исключений.
  • Используется с Thread или ExecutorService.
Runnable task = () -> {
    System.out.println("Задача выполняется в потоке: " + Thread.currentThread().getName());
};
// Запуск через Thread
new Thread(task).start();
// Или через ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(task);

2. Интерфейс Callable<V>

  • Определяет задачу, которая возвращает результат типа V и может бросать проверяемые исключения.
  • Используется только с ExecutorService, который возвращает объект Future<V> для получения результата.
Callable<Integer> computation = () -> {
    TimeUnit.SECONDS.sleep(1);
    return 42;
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> futureResult = executor.submit(computation);

// Блокирующее получение результата
Integer result = futureResult.get();
System.out.println("Результат: " + result); // Результат: 42

Ключевое отличие: Callable предпочтительнее, когда задача должна вернуть результат или требует обработки проверяемых исключений в рамках механизма Future.

Ответ 18+ 🔞

Так, слушай, про потоки. Есть две главные штуки, как задачу в отдельную нитку сунуть — Runnable и Callable<V>. Разница, блядь, принципиальная, сейчас разжуёшь.

1. Runnable — старый добрый дед

Этот интерфейс, сука, как тот немой Герасим — работает молча, нихуя не возвращает и проверяемые исключения наружу выплюнуть не может, только проглотить. Кинул задачу — и забыл. Используется либо с голым Thread, либо с ExecutorService.

Runnable task = () -> {
    System.out.println("Задача выполняется в потоке: " + Thread.currentThread().getName());
};
// Запуск через Thread
new Thread(task).start();
// Или через ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(task);

Вот и всё, ебушки-воробушки. Запустил и пошёл дальше. Результата не жди — его просто нет.

2. Callable<V> — интеллигент с возвратом

А вот это уже интереснее. Этот чувак может вернуть результат (любого типа V) и, внимание, имеет право заявить: «Ёпта, у меня тут InterruptedException вылез!». Работает ТОЛЬКО с ExecutorService, который возвращает тебе Future<V> — такую расписку, что результат будет, но позже.

Callable<Integer> computation = () -> {
    TimeUnit.SECONDS.sleep(1); // Может спать, может исключение кидать — ему можно!
    return 42; // А главное — вернёт ответ!
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> futureResult = executor.submit(computation);

// Тут поток заблокируется, пока результат не приплывёт
Integer result = futureResult.get();
System.out.println("Результат: " + result); // Результат: 42

Короче, суть в чём: если тебе нужно просто что-то сделать в фоне и плевать на ответ — бери Runnable. Если же задача должна что-то вернуть или у неё есть проверяемые исключения, которые ты хочешь через Future обработать — твой выбор Callable. Всё просто, как три копейки, блядь.