← Назад к вопросам
Какие есть способы создания и запуска потоков в 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 вместо прямого создания потоков.