Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что делает join()?
Метод join() используется для синхронизации потоков. Он позволяет одному потоку дождаться завершения другого потока перед продолжением своего выполнения.
Базовое использование
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
System.out.println("Рабочий начал");
try {
Thread.sleep(2000); // имитация работы
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Рабочий завершил");
});
worker.start(); // запуск потока
System.out.println("Главный ждёт...");
worker.join(); // главный поток ЖДЁТ завершения worker
System.out.println("Рабочий закончил, главный продолжает");
}
}
// Вывод:
// Главный ждёт...
// Рабочий начал
// Рабочий завершил
// Рабочий закончил, главный продолжает
Сигнатуры методов
public final void join() throws InterruptedException
// Ждёт бесконечно (пока поток не завершится)
public final void join(long millis) throws InterruptedException
// Ждёт до millis миллисекунд
public final void join(long millis, int nanos) throws InterruptedException
// Ждёт до millis миллисекунд + nanos наносекунд
Сценарий с таймаутом
public class JoinWithTimeout {
public static void main(String[] args) throws InterruptedException {
Thread slowWorker = new Thread(() -> {
System.out.println("Работник начал");
try {
Thread.sleep(5000); // долгая операция
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Работник закончил");
});
slowWorker.start();
System.out.println("Главный ждёт максимум 2 секунды");
slowWorker.join(2000); // ждём только 2 секунды
// После 2 секунд главный продолжит, даже если worker не закончил
if (slowWorker.isAlive()) {
System.out.println("Время истекло, worker ещё работает");
}
}
}
// Вывод:
// Главный ждёт максимум 2 секунды
// Работник начал
// Время истекло, worker ещё работает
// Работник закончил (позже)
Обработка InterruptedException
public class JoinInterruptionExample {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
System.out.println("Рабочий работает");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("Рабочий был прерван");
}
});
worker.start();
Thread.sleep(1000);
worker.interrupt(); // прерывает ожидающий в sleep поток
worker.join(); // ждём завершения
System.out.println("Готово");
}
}
Множественные потоки
public class MultipleThreadJoin {
public static void main(String[] args) throws InterruptedException {
Thread[] workers = new Thread[3];
for (int i = 0; i < 3; i++) {
final int threadId = i;
workers[i] = new Thread(() -> {
System.out.println("Поток " + threadId + " начал");
try {
Thread.sleep((long) (Math.random() * 2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Поток " + threadId + " завершил");
});
workers[i].start();
}
// Ждём завершения всех потоков
for (Thread worker : workers) {
worker.join();
}
System.out.println("Все потоки завершены");
}
}
// Вывод:
// Поток 0 начал
// Поток 1 начал
// Поток 2 начал
// Поток 0 завершил
// Поток 1 завершил
// Поток 2 завершил
// Все потоки завершены
Практический пример: Map-Reduce паттерн
public class MapReduceExample {
public static void main(String[] args) throws InterruptedException {
int[] data = {1, 2, 3, 4, 5, 6, 7, 8};
int[] partialSums = new int[2];
// Создаём 2 рабочих потока
Thread worker1 = new Thread(() -> {
// Считаем сумму первой половины
for (int i = 0; i < 4; i++) {
partialSums[0] += data[i];
}
});
Thread worker2 = new Thread(() -> {
// Считаем сумму второй половины
for (int i = 4; i < 8; i++) {
partialSums[1] += data[i];
}
});
worker1.start();
worker2.start();
worker1.join(); // ждём завершения первого
worker2.join(); // ждём завершения второго
int totalSum = partialSums[0] + partialSums[1];
System.out.println("Общая сумма: " + totalSum);
}
}
Важные моменты
- Блокирующая операция: join() блокирует текущий поток до завершения целевого
- Исключение: throws InterruptedException — всегда нужно обрабатывать
- Порядок выполнения: join() гарантирует порядок (Memory Visibility через happens-before)
- Не использовать с главным потоком: может привести к deadlock
- Альтернативы: CountDownLatch, CyclicBarrier, Phaser
Когда использовать join()
- Ожидание завершения рабочих потоков
- Синхронизация stages вычисления
- Shutdown graceful закрытие приложения
- Тестирование многопоточного кода
join() — это простой и надёжный способ синхронизации потоков, хотя для сложных сценариев часто используют более специализированные инструменты из java.util.concurrent.