Комментарии (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();
}
}