Отслеживание окончания выполнения потоков в Java: синхронизация

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Для проверки успешного завершения работы других потоков вы можете использовать метод join() для каждого из них. Такой вызов приостановит выполнение текущего потока до тех пор, пока все остальные потоки не завершат свою работу.

Java
Скопировать код
// Создание двух потоков с различными задачами
Thread t1 = new Thread(() -> { /* код выполнения сложной задачи */ });
Thread t2 = new Thread(() -> { /* код решения важной проблемы */ });

// Запуск потоков
t1.start();
t2.start();

// Ожидание окончания работы потока t1
t1.join();

// Ожидание окончания работы потока t2
t2.join();

// В этом месте мы можем быть уверены, что оба потока завершили свою работу

Использование метода join() гарантирует, что потоки t1 и t2 реализуют свои задачи до того, как основной поток продолжит выполнение.

Для выполнения и отслеживания сложных задач можно прибегнуть к ExecutorService и Futures. Это позволит ожидать результатов выполнения и избежать недостатков "волшебных" предсказаний.

Java
Скопировать код
// Создание пула из 4 потоков
ExecutorService executor = Executors.newFixedThreadPool(4); 
Callable<String> task = () -> {/* ваш код для достижения желаемого результата */};

// Посылаем задание на исполнение и получаем Future для отслеживания статуса и результата
Future<String> future = executor.submit(task); 

// Завершаем работу исполнителя после выполнения всех заданий
executor.shutdown();
// Ожидаем всей работы исполнителя в течение часа
executor.awaitTermination(1, TimeUnit.HOURS);
Кинга Идем в IT: пошаговый план для смены профессии

Параллелизм в мире Java

Используйте инструменты для параллельной работы

Пакет java.util.concurrent предлагает мощные инструменты для управления параллельным выполнением кода в Java.

  • CountDownLatch: Механизм для синхронизации потоков, позволяющий одному потоку ожидать завершение работы других потоков. Может быть полезен, например, если вы хотите совместно посмотреть фильм с друзьями.
Java
Скопировать код
// Ожидание двух друзей
CountDownLatch latch = new CountDownLatch(2);  
new Thread(() -> { /* просмотр фильма */ latch.countDown(); }).start();
new Thread(() -> { /* просмотр фильма */ latch.countDown(); }).start();

// Ожидание окончания просмотра фильма всеми потоками
latch.await();
  • CyclicBarrier: Инструмент, позволяющий группе потоков одновременно достичь определённой точки и продолжить выполнение своей работы.

  • ExecutorService: Сервис для управления потоками, обеспечивающий функциональность для эффективной отправки и синхронизации задач, такие как invokeAll().

Бережное обращение с исключениями

Для обработки исключений в потоках можно использовать setUncaughtExceptionHandler:

Java
Скопировать код
Thread thread = new Thread(() -> {
    throw new RuntimeException("Ошибка в выполнении!"); // Генерация исключения
});
thread.setUncaughtExceptionHandler((t, e) -> System.out.println(t + " выдает ошибку: " + e)); // Обработка исключения
thread.start();

Возвращайте результаты через Callable

Воспользуйтесь java.util.concurrent.Callable для получения результатов выполнения потоков. Обычный Thread, к сожалению, не дает такой возможности.

Java
Скопировать код
ExecutorService executorService = Executors.newSingleThreadExecutor();
// Запуск потока с возможностью возврата результата
Future<Integer> future = executorService.submit(() -> 42); 

// Получаем результат выполнения
Integer answer = future.get();

Избегайте ошибок при параллельном выполнении

Взаимные блокировки (Deadlocks): Соблюдайте порядок накладывания и снятия блокировок и инкапсулируйте потоки при помощи встроенных в язык конструкций.

Утечки потоков (Thread Leaks): Внимательно контролируйте жизненный цикл потоков и не забывайте о завершении их работы, чтобы системные ресурсы не оставались заблокированными из-за неоправданной работы потоков.

Состояние гонки (Race Conditions): Используйте инструменты для синхронизации и потокобезопасные классы, чтобы минимизировать вероятность ошибок в данных, вызванных одновременным доступом к ним.

Визуализация

Получение информации о завершении работы потоков можно представить как забег с множеством участников:

Markdown
Скопировать код
Трасса (🏁): | 🏎️ |  🚁 | 🚀 | 🚂 |
Потоки       : [Поток 1, Поток 2, Поток 3, Поток 4]
Финиш        : 🎌

Основной поток — это судья забега, который оставляется ждать у финишной линии:

Markdown
Скопировать код
👀 Судья (Основной поток) постоянно контролирует ситуацию и ожидает, пока все участники (потоки) не пересекут финишную линию

Как только все участники пересекут финишную черту:

Markdown
Скопировать код
🏁: | 🎌 | 🎌 | 🎌 | 🎌 |
Все потоки закончили работу?: [✅, ✅, ✅, ✅]

Судья объявляет об окончании забега:

Markdown
Скопировать код
👀: Все участники (потоки) финишировали! 🎉Празднуем!

Лучшие практики для параллельного выполнения кода

Используйте Runnable вместо наследования от Thread

Лучше реализуйте Runnable и передавайте его экземпляры в Thread, а не расширяйте сам класс Thread. Это позволит вашему коду стать более структурированным и гибким.

Java
Скопировать код
public class MyTask implements Runnable {
    private final CompletableFuture<Void> doneSignal;

    MyTask(CompletableFuture<Void> doneSignal) {
        this.doneSignal = doneSignal;
    }

    @Override
    public void run() {
        // Код обработки задачи
        doneSignal.complete(null); // Сигнал о завершении работы
    }
}

CompletableFuture<Void> signal = new CompletableFuture<>();
// Запуск потока с задачей
new Thread(new MyTask(signal)).start();

// Ожидание завершения задачи в потоке
signal.join();

Полезные материалы

  1. ExecutorService (Java Platform SE 8) — обзор и примеры использования метода awaitTermination для ожидания завершения потоков.
  2. Урок: Параллелизм (Java Tutorials > Essential Classes) — официальное руководство по использованию инструментов параллелизма в Java.
  3. CyclicBarrier — подробный анализ работы с CyclicBarrier.
  4. Joins (Java Tutorials > Essential Classes > Concurrency) — руководство по использованию метода join для синхронизации потоков в Java.
  5. Пулы потоков (Java Tutorials > Essential Classes > Concurrency) — рекомендации по управлению потоками с использованием пулов потоков.
  6. Синхронизация (Java Tutorials > Essential Classes > Concurrency) — соображения о важности синхронизации для обеспечения безопасности потоков.
  7. Phaser (Java Platform SE 7) — пояснения о синхронизации задач в потоках с использованием класса Phaser.
Свежие материалы