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

Какие есть способы создания и запуска потоков в Java?

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

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

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

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

Способы создания и запуска потоков в Java

Основные методы

В Java есть несколько способов создания и запуска потоков. Рассмотрим каждый подробно.

1. Наследование от Thread

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Поток работает: " + Thread.currentThread().getName());
        for (int i = 0; i < 5; i++) {
            System.out.println("Итерация: " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

// Запуск:
MyThread thread = new MyThread();
thread.setName("MyWorker");
thread.start();  // ВАЖНО: start(), не run()!

Недостатки:

  • Java поддерживает только одиночное наследование
  • Если класс уже наследует другой класс, этот способ невозможен
  • Менее гибкий

2. Реализация интерфейса Runnable (рекомендуемо)

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Поток работает");
    }
}

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

// Или с lambda (Java 8+):
Thread thread = new Thread(() -> {
    System.out.println("Поток работает");
});
thread.start();

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

  • Класс может наследовать любой другой класс
  • Чище и понятнее
  • Лучшая практика в современной Java

3. ExecutorService (современный подход)

import java.util.concurrent.*;

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

// Запуск задач
for (int i = 0; i < 10; i++) {
    executor.execute(() -> {
        System.out.println("Задача " + Thread.currentThread().getName());
    });
}

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

Типы пулов:

// Фиксированный размер
ExecutorService fixed = Executors.newFixedThreadPool(4);

// Кэшированный пул (создаёт потоки по мере надобности)
ExecutorService cached = Executors.newCachedThreadPool();

// Один поток
ExecutorService single = Executors.newSingleThreadExecutor();

// Планируемый пул (для задач с задержкой)
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);

// Параллельный поток-пул (оптимизирован для parallel streams)
ForkJoinPool pool = ForkJoinPool.commonPool();

4. Callable и Future (с результатом)

import java.util.concurrent.*;

Callable<Integer> task = () -> {
    Thread.sleep(2000);
    return 42;  // Возвращаем результат
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);

try {
    Integer result = future.get();  // Ждёт результат
    System.out.println("Результат: " + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
} finally {
    executor.shutdown();
}

5. CompletableFuture (Java 8+, асинхронный код)

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    System.out.println("Асинхронная работа");
    return 42;
});

// Обработка результата
future
    .thenApply(result -> result * 2)
    .thenAccept(result -> System.out.println("Результат: " + result))
    .exceptionally(ex -> {
        System.out.println("Ошибка: " + ex.getMessage());
        return null;
    });

// Ожидание (если нужно)
future.join();

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

  • Функциональный стиль
  • Комбинирование асинхронных операций
  • Обработка исключений
  • Цепочки операций

6. ForkJoinPool (для divide-and-conquer)

import java.util.concurrent.*;

class SumTask extends RecursiveTask<Long> {
    private static final int THRESHOLD = 1000;
    private long[] array;
    private int start, end;
    
    public SumTask(long[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }
    
    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            return sum;
        }
        
        int mid = (start + end) / 2;
        SumTask left = new SumTask(array, start, mid);
        SumTask right = new SumTask(array, mid, end);
        
        left.fork();  // Запустить параллельно
        long rightResult = right.compute();
        long leftResult = left.join();
        
        return leftResult + rightResult;
    }
}

// Использование
long[] data = new long[10000];
ForkJoinPool pool = new ForkJoinPool();
long result = pool.invoke(new SumTask(data, 0, data.length));

7. Stream API для параллельной обработки

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);

// Последовательный:
int sum1 = numbers.stream()
    .map(n -> n * 2)
    .reduce(0, Integer::sum);

// Параллельный (использует ForkJoinPool):
int sum2 = numbers.parallelStream()
    .map(n -> n * 2)
    .reduce(0, Integer::sum);

Основные операции с потоками

// Проверка, живой ли поток
if (thread.isAlive()) {
    System.out.println("Поток ещё работает");
}

// Ожидание завершения
try {
    thread.join();  // Главный поток ждёт завершения thread
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

// Приоритет (1-10, 5 - нормальный)
thread.setPriority(Thread.MAX_PRIORITY);

// Демон-потоки (завершаются с JVM)
thread.setDaemon(true);
thread.start();

// Получение текущего потока
Thread current = Thread.currentThread();
System.out.println(current.getName());

Сравнение подходов

┌──────────────────┬──────────┬──────────┬──────────────┐
│ Способ           │ Простота │ Гибкость │ Масштабируемость │
├──────────────────┼──────────┼──────────┼──────────────┤
│ Thread class     │ Низкая   │ Средняя  │ Средняя      │
│ Runnable + Thread│ Средняя  │ Высокая  │ Средняя      │
│ ExecutorService  │ Средняя  │ Высокая  │ Высокая      │
│ CompletableFuture│ Высокая  │ Высокая  │ Высокая      │
│ ForkJoinPool     │ Высокая  │ Средняя  │ Очень высокая│
│ Stream.parallel()│ Высокая  │ Низкая   │ Высокая      │
└──────────────────┴──────────┴──────────┴──────────────┘

Best Practices

// 1. Никогда не вызывай run() напрямую!
MyThread thread = new MyThread();
thread.run();    // ❌ Выполнится в текущем потоке
thread.start();  // ✅ Создаст новый поток

// 2. Используй ExecutorService вместо создания потоков вручную
// ❌ Плохо
for (int i = 0; i < 1000; i++) {
    new Thread(() -> doWork()).start();
}

// ✅ Хорошо
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
    executor.execute(() -> doWork());
}

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

// 4. Используй CompletableFuture для асинхронного кода
CompletableFuture
    .supplyAsync(() -> fetchData())
    .thenApply(this::processData)
    .thenAccept(this::saveResult);

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

  • Простая задача в одном потоке → Runnable + new Thread()
  • Много независимых задач → ExecutorService.newFixedThreadPool()
  • Нужен результат → Callable + Future или CompletableFuture
  • Асинхронный код → CompletableFuture
  • Параллельная обработка данных → stream.parallelStream() или ForkJoinPool
  • Повторяющиеся задачи с задержкой → ScheduledExecutorService

Современная Java настоятельно рекомендует использовать ExecutorService или CompletableFuture вместо прямого создания потоков.

Какие есть способы создания и запуска потоков в Java? | PrepBro