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

Какие знаешь способы запуска потока?

1.6 Junior🔥 191 комментариев
#Многопоточность

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

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

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

Способы запуска потока в Java

Многопоточность — критическая часть современной Java разработки. Существует несколько подходов к созданию и запуску потоков, каждый с собственными преимуществами и недостатками.

Способ 1: Расширение класса Thread

Класс наследуется от Thread, и переопределяется метод run():

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Поток выполняется: " + Thread.currentThread().getName());
    }
}

// Запуск
MyThread thread = new MyThread();
thread.start(); // ✅ Правильно — создает новый поток
// thread.run(); // ❌ Неправильно — вызывает в текущем потоке!

Характеристики:

  • Простой синтаксис
  • Но наследует один класс, что ограничивает архитектуру
  • В Java допускается только одиночное наследование

Пример с параметрами:

class CounterThread extends Thread {
    private int count;
    
    public CounterThread(String name, int count) {
        super(name);
        this.count = count;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < count; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(100); // Пауза 100ms
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

// Запуск
CounterThread t1 = new CounterThread("Counter-1", 5);
CounterThread t2 = new CounterThread("Counter-2", 5);
t1.start();
t2.start();

Способ 2: Реализация интерфейса Runnable

Более гибкий подход. Класс реализует Runnable и передается в Thread:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable выполняется: " + Thread.currentThread().getName());
    }
}

// Запуск
Thread thread = new Thread(new MyRunnable());
thread.start();

// Или с Lambda выражением (Java 8+)
Thread thread = new Thread(() -> {
    System.out.println("Lambda поток");
});
thread.start();

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

  • Можно наследовать от другого класса
  • Более модульный подход
  • Позволяет использовать lambdas

Пример с параметрами:

class MyTask implements Runnable {
    private String message;
    
    public MyTask(String message) {
        this.message = message;
    }
    
    @Override
    public void run() {
        System.out.println(message);
    }
}

// Запуск
for (int i = 0; i < 3; i++) {
    new Thread(new MyTask("Task " + i)).start();
}

Способ 3: Lambda выражения (Java 8+)

Самый современный и компактный способ:

// Простой лямбда
new Thread(() -> System.out.println("Lambda thread")).start();

// С логикой
new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("Count: " + i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}).start();

Способ 4: Thread Pool (ExecutorService)

Самый часто используемый в production способ. Управляет пулом потоков:

// Создание пула с 5 потоками
ExecutorService executor = Executors.newFixedThreadPool(5);

// Отправка задачи
executor.submit(() -> {
    System.out.println("Task executed in: " + Thread.currentThread().getName());
});

// Завершение работы
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);

Типы пулов:

// FixedThreadPool — фиксированное количество потоков
ExecutorService fixed = Executors.newFixedThreadPool(5);

// CachedThreadPool — создает потоки по необходимости
ExecutorService cached = Executors.newCachedThreadPool();

// SingleThreadExecutor — один поток для всех задач
ExecutorService single = Executors.newSingleThreadExecutor();

// ScheduledExecutorService — для периодических задач
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);
scheduled.scheduleAtFixedRate(() -> System.out.println("Task"), 0, 1, TimeUnit.SECONDS);

// ForkJoinPool — для рекурсивных задач (Java 7+)
ForkJoinPool pool = ForkJoinPool.commonPool();

Пример с Future:

ExecutorService executor = Executors.newFixedThreadPool(3);

// Отправка задачи, которая возвращает результат
Future<Integer> future = executor.submit(() -> {
    Thread.sleep(1000);
    return 42;
});

try {
    Integer result = future.get(); // Блокирует до получения результата
    System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

executor.shutdown();

Способ 5: Callable и ExecutorService

Для задач, которые возвращают результат:

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        return "Result from callable";
    }
}

// Запуск
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());

System.out.println(future.get()); // Блокирует и возвращает результат
executor.shutdown();

Способ 6: Virtual Threads (Java 19+, Preview)

Легковесные потоки для очень большого количества параллельных задач:

// Virtual Thread — миллионы потоков, малый оверхед
Thread vt = Thread.ofVirtual()
    .name("virtual-thread")
    .start(() -> System.out.println("Virtual thread"));

// Или через ExecutorService
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 1000000; i++) {
    executor.submit(() -> {
        // Выполняется в virtual thread
    });
}
executor.close();

Сравнение способов

СпособПростотаПроизводительностьКогда использовать
Thread расширениеВысокаяСредняяРедко, обучение
RunnableВысокаяСредняяПростые задачи
LambdaВысокаяСредняяКомпактный код
ExecutorServiceСредняяВысокаяProduction, рекомендуется
CallableСредняяВысокаяНужны результаты
Virtual ThreadsСредняяОчень высокаяМасштабные приложения

Важные правила

// ✅ ПРАВИЛЬНО: используйте start()
new Thread(() -> {}).start();

// ❌ НЕПРАВИЛЬНО: вызов run() напрямую
new Thread(() -> {}).run(); // Выполняется в текущем потоке!

// ✅ ПРАВИЛЬНО: используйте ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.submit(() -> {});
executor.shutdown();

// ❌ НЕПРАВИЛЬНО: создание потока в цикле без пула
for (int i = 0; i < 1000; i++) {
    new Thread(() -> {}).start(); // Утечка потоков и памяти!
}

Обработка исключений в потоках

// Способ 1: try-catch внутри run()
new Thread(() -> {
    try {
        // Код
    } catch (Exception e) {
        e.printStackTrace();
    }
}).start();

// Способ 2: UncaughtExceptionHandler
Thread thread = new Thread(() -> {
    throw new RuntimeException("Error!");
});
thread.setUncaughtExceptionHandler((t, e) -> {
    System.out.println("Exception in thread: " + e.getMessage());
});
thread.start();

// Способ 3: ExecutorService обрабатывает исключения в Callable
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
    throw new RuntimeException("Error in task");
});

try {
    future.get();
} catch (ExecutionException e) {
    System.out.println("Task failed: " + e.getCause());
}

Best Practices

// 1. Используйте ExecutorService, а не создавайте потоки вручную
ExecutorService executor = Executors.newFixedThreadPool(10);

// 2. Правильно завершайте ExecutorService
executor.shutdown();
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
    executor.shutdownNow();
}

// 3. Используйте Lambda для простых Runnable
new Thread(() -> doSomething()).start();

// 4. Для результатов используйте Callable
Future<Result> future = executor.submit(() -> computeResult());

// 5. Обрабатывайте InterruptedException правильно
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // Восстановите interrupt flag
}

// 6. Избегайте busy waiting
while (!taskComplete) {
    // ❌ Плохо
}

// Используйте вместо этого синхронизацию
synchronized (lock) {
    while (!taskComplete) {
        lock.wait();
    }
}