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

Какие знаешь примитивы в библиотеке java.util.concurrent?

2.0 Middle🔥 241 комментариев
#JVM и управление памятью#Многопоточность

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

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

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

Примитивы java.util.concurrent

java.util.concurrent — это пакет для многопоточного программирования, предоставляющий потокобезопасные примитивы синхронизации. Эти инструменты гораздо удобнее, чем низкоуровневые synchronized и wait/notify.

1. CountDownLatch

Позволяет потокам ждать, пока другие потоки выполнят определенное количество операций.

CountDownLatch latch = new CountDownLatch(3);

// Поток 1: ждёт
new Thread(() -> {
    try {
        latch.await(); // Блокируется, пока счётчик не станет 0
        System.out.println("Все задачи выполнены!");
    } catch (InterruptedException e) {}
}).start();

// Потоки 2-4: уменьшают счётчик
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        System.out.println("Работаю...");
        latch.countDown(); // Уменьшить счётчик на 1
    }).start();
}

Когда использовать: ожидание завершения параллельных задач (например, инициализация сервиса, ожидание загрузки всех данных).

2. CyclicBarrier

Позволяет группе потоков ждать друг друга в определённой точке (барьере).

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("Все потоки достигли барьера!");
});

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        System.out.println("Поток работает...");
        try {
            barrier.await(); // Ждёт остальные потоки
        } catch (Exception e) {}
        System.out.println("Продолжаю после барьера");
    }).start();
}

Отличие от CountDownLatch: CyclicBarrier переиспользуется (можно вызвать await() несколько раз), CountDownLatch одноразовый.

3. Semaphore

Ограничивает количество потоков, которые могут одновременно получить доступ к ресурсу.

Semaphore semaphore = new Semaphore(2); // Максимум 2 потока одновременно

for (int i = 0; i < 5; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire(); // Получить разрешение
            System.out.println(Thread.currentThread().getName() + " работает");
            Thread.sleep(2000);
        } catch (InterruptedException e) {}
        finally {
            semaphore.release(); // Отпустить разрешение
        }
    }).start();
}

Применение: ограничение одновременных HTTP запросов, доступ к ограниченному пулу ресурсов.

4. ReentrantLock

Потокобезопасный замок, более гибкий, чем synchronized. Позволяет:

  • Попытаться захватить с таймаутом
  • Проверить, не захвачен ли уже
  • Использовать Condition для ожидания
ReentrantLock lock = new ReentrantLock();

new Thread(() -> {
    lock.lock();
    try {
        System.out.println("Критическая секция");
        Thread.sleep(3000);
    } finally {
        lock.unlock();
    }
}).start();

// Попытаться захватить с таймаутом
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // Работаем
    } finally {
        lock.unlock();
    }
}

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

  • tryLock() с таймаутом
  • Проверка захватов: isLocked(), getHoldCount()
  • Fairness (справедливое распределение)

5. ReadWriteLock (ReentrantReadWriteLock)

Позволяет множеству потоков одновременно читать, но только одному потоку писать.

ReadWriteLock rwLock = new ReentrantReadWriteLock();
String data = "initial";

// Несколько читателей одновременно
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        rwLock.readLock().lock();
        try {
            System.out.println("Читаю: " + data);
            Thread.sleep(2000);
        } finally {
            rwLock.readLock().unlock();
        }
    }).start();
}

// Один писатель
new Thread(() -> {
    rwLock.writeLock().lock();
    try {
        data = "updated";
        System.out.println("Написал новое значение");
    } finally {
        rwLock.writeLock().unlock();
    }
}).start();

6. Phaser

Регулирует поток потоков через несколько фаз.

Phaser phaser = new Phaser(3);

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        System.out.println("Фаза 1");
        phaser.arriveAndAwaitAdvance(); // Ждём остальных
        
        System.out.println("Фаза 2");
        phaser.arriveAndAwaitAdvance();
        
        System.out.println("Готово");
        phaser.arriveAndDeregister();
    }).start();
}

7. Exchanger

Позволяет двум потокам обменяться данными в определённой точке.

Exchanger<String> exchanger = new Exchanger<>();

new Thread(() -> {
    try {
        String data1 = "Данные от потока 1";
        String data2 = exchanger.exchange(data1);
        System.out.println("Получил: " + data2);
    } catch (InterruptedException e) {}
}).start();

new Thread(() -> {
    try {
        String data1 = "Данные от потока 2";
        String data2 = exchanger.exchange(data1);
        System.out.println("Получил: " + data2);
    } catch (InterruptedException e) {}
}).start();

Таблица сравнения примитивов

ПримитивНазначениеОдноразовый?
CountDownLatchЖдать N событийДа
CyclicBarrierСинхронизация в точкеНет (переиспользуемый)
SemaphoreОграничить одновременный доступНет
ReentrantLockВзаимное исключениеНет
ReadWriteLockМного читателей, один писательНет
PhaserМногофазная синхронизацияНет
ExchangerОбмен данными между двумя потокамиНет

Лучшие практики

  • ✅ Всегда используй finally или try-with-resources для освобождения ресурсов
  • ✅ Предпочитай эти примитивы вместо synchronized и wait/notify
  • ✅ Избегай deadlock: захватывай ресурсы в одинаковом порядке
  • ✅ Используй tryLock() вместо lock() для избежания бесконечных блокировок

Эти примитивы — основа для построения надежного многопоточного кода в Java.