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

Для чего нужен Runnable?

1.3 Junior🔥 211 комментариев
#Многопоточность#Основы Java

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

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

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

# Runnable в Java

Runnable — это один из фундаментальных интерфейсов для работы с многопоточностью в Java. Это вопрос, который часто задают потому, что многопоточность — критичный навык для Java разработчика.

Что такое Runnable

Runnable — это функциональный интерфейс, который определяет одиночный метод:

public interface Runnable {
    void run();  // Единственный метод
}

Это контракт: объект, который реализует Runnable, может быть выполнен в отдельном потоке.

Зачем он нужен

Runnable нужен для того, чтобы определить код, который должен быть выполнен в отдельном потоке (Thread). Это основа многопоточности в Java.

Базовый пример

// Способ 1: Реализуем интерфейс
public class MyTask implements Runnable {
    public void run() {
        System.out.println("Выполняется в отдельном потоке");
        for (int i = 0; i < 5; i++) {
            System.out.println("i = " + i);
        }
    }
}

// Использование
MyTask task = new MyTask();
Thread thread = new Thread(task);
thread.start();  // Запускаем поток

// Способ 2: Lambda выражение (Java 8+)
Thread thread = new Thread(() -> {
    System.out.println("Выполняется в отдельном потоке");
    for (int i = 0; i < 5; i++) {
        System.out.println("i = " + i);
    }
});
thread.start();

Синхронный vs асинхронный код

Синхронный код (без Runnable)

public static void main(String[] args) {
    System.out.println("Начало");
    
    // Эта операция блокирует main поток
    doLongOperation();
    
    System.out.println("Конец");  // Выполнится только после doLongOperation
}

private static void doLongOperation() {
    Thread.sleep(5000);  // Ждем 5 секунд
}

Проблема: UI зависнет на 5 секунд.

Асинхронный код (с Runnable)

public static void main(String[] args) {
    System.out.println("Начало");
    
    // Запускаем в отдельном потоке
    Thread thread = new Thread(() -> {
        doLongOperation();
    });
    thread.start();
    
    System.out.println("Конец");  // Выполнится сразу
}

private static void doLongOperation() {
    Thread.sleep(5000);  // Ждем 5 секунд в отдельном потоке
}

Преимущество: main поток продолжает работать, UI не зависает.

Реальный пример: веб-приложение

@GetMapping("/data")
public void fetchData() {
    // Дорогостоящая операция (IO, БД запрос)
    // Не должна блокировать HTTP thread
    
    Thread thread = new Thread(() -> {
        List<Data> data = fetchFromDatabase();
        sendEmailWithResults(data);
        logResults(data);
    });
    thread.start();
    
    // Вернули ответ пользователю сразу
    return "Processing started";
}

Runnable vs Callable

Runnable

  • Ничего не возвращает (void run())
  • Не может выбросить проверяемые исключения
Runnable task = () -> {
    System.out.println("Выполняется");
};

Callable (для операций с результатом)

  • Возвращает результат (V call())
  • Может выбросить Exception
Callable<Integer> task = () -> {
    return 42;  // Возвращаем результат
};

ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(task);
Integer result = future.get();  // Ждем результат

Runnable с ExecutorService (рекомендуется)

Вместо создания новых Thread объектов, лучше использовать thread pool:

// Создаем pool из 4 потоков
ExecutorService executor = Executors.newFixedThreadPool(4);

// Отправляем 10 задач
for (int i = 0; i < 10; i++) {
    executor.submit(() -> {
        doWork();  // Выполнится в одном из потоков pool
    });
}

// Ждем завершения всех задач
executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
    executor.shutdownNow();
}

Преимущества:

  • Переиспользование потоков (экономия ресурсов)
  • Ограничение количества потоков
  • Управление жизненным циклом

Практический пример: Download Manager

public class DownloadManager {
    private ExecutorService executor = Executors.newFixedThreadPool(4);
    
    public void downloadFile(String url) {
        executor.submit(() -> {
            try {
                byte[] data = fetchData(url);
                saveToFile(url, data);
                System.out.println("Downloaded: " + url);
            } catch (Exception e) {
                System.err.println("Error downloading: " + e);
            }
        });
    }
    
    public void shutdown() {
        executor.shutdown();
    }
}

// Использование
DownloadManager manager = new DownloadManager();
manager.downloadFile("https://example.com/file1.pdf");
manager.downloadFile("https://example.com/file2.pdf");
manager.downloadFile("https://example.com/file3.pdf");
// Все 3 файла скачиваются параллельно

Важные замечания о многопоточности

Race Conditions

int counter = 0;

// Два потока увеличивают counter
Thread t1 = new Thread(() -> counter++);
Thread t2 = new Thread(() -> counter++);

t1.start();
t2.start();

// Результат может быть 1 вместо 2! (race condition)

Решение: synchronized или atomic операции

AtomicInteger counter = new AtomicInteger(0);

Thread t1 = new Thread(() -> counter.incrementAndGet());
Thread t2 = new Thread(() -> counter.incrementAndGet());

t1.start();
t2.start();
// Теперь безопасно

Когда использовать Runnable

✓ Асинхронные операции (file I/O, network requests) ✓ Задачи, которые не должны блокировать UI ✓ Параллельная обработка (с ExecutorService) ✓ Background работы (logging, cleanup) ✓ Event processing

Когда НЕ использовать

✗ Если нужен результат — используй Callable ✗ Если простая синхронная операция ✗ Если нужна полная очередь задач — используй ScheduledExecutorService ✗ Если нужна реактивная архитектура — используй reactive libraries

Выводы

  1. Runnable — основа многопоточности в Java
  2. Позволяет выполнять код в отдельном потоке
  3. Не блокирует основной поток
  4. Используй ExecutorService вместо создания новых Thread
  5. Помни о race conditions при доступе к общим ресурсам
  6. Lambda выражения делают Runnable удобнее (Java 8+)

Multithreading — это сложно, но Runnable — это первый шаг к пониманию параллелизма в Java.