0Pricing
Java Academy · Урок

ReentrantLock и synchronized

Сравните возможности ReentrantLock (tryLock, lockInterruptibly, справедливость) с ключевым словом synchronized.

«ReentrantLock и synchronized» — бесплатный урок Java Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Ключевое слово синхронизации

Ключевое слово synchronized в Java обеспечивает взаимное исключение с помощью внутренней блокировки объекта (монитора). Это простой, но ограниченный механизм — без ограничения времени ожидания, справедливого доступа и возможности прерывания.

class Counter {
    private int count = 0;
    
    synchronized void increment() {
        count++; // only one thread at a time
    }
    
    synchronized int get() { return count; }
}

Counter c = new Counter();
// Multiple threads can safely call increment()

Основы ReentrantLock

ReentrantLock — это явная блокировка из java.util.concurrent.locks. Она обеспечивает такое же взаимное исключение, как и synchronized, но предоставляет больше возможностей управления. Блокировку необходимо вручную освобождать в блоке finally.

import java.util.concurrent.locks.*;

class SafeCounter {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // always release in finally!
        }
    }
    
    int get() {
        lock.lock();
        try { return count; }
        finally { lock.unlock(); }
    }
}

tryLock: неблокирующий захват

tryLock() пытается захватить блокировку, не блокируя поток. Возвращает положительный результат при успехе и отрицательный — если блокировка занята другим потоком.

ReentrantLock lock = new ReentrantLock();

if (lock.tryLock()) {
    try {
        System.out.println("Got the lock, doing work");
    } finally {
        lock.unlock();
    }
} else {
    System.out.println("Lock busy, skipping or retrying");
}

tryLock с ограничением времени

Ожидайте заданное время, чтобы захватить блокировку:

try {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            // work with protected resource
            System.out.println("Acquired within 500ms");
        } finally {
            lock.unlock();
        }
    } else {
        System.out.println("Timed out waiting for lock");
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

lockInterruptibly

lockInterruptibly() захватывает блокировку, если поток не был прерван. Это полезно для отменяемых задач, ожидающих блокировку:

try {
    lock.lockInterruptibly(); // throws InterruptedException if interrupted
    try {
        // do work
    } finally {
        lock.unlock();
    }
} catch (InterruptedException e) {
    System.out.println("Interrupted while waiting for lock");
    Thread.currentThread().interrupt();
}

Повторный захват

И synchronized, и ReentrantLock допускают повторный захват: поток, который уже владеет блокировкой, может захватить её снова без взаимной блокировки:

synchronized void outer() {
    System.out.println("outer");
    inner(); // same thread re-enters — OK
}

synchronized void inner() {
    System.out.println("inner"); // same lock, same thread
}

// ReentrantLock:
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.lock(); // acquire again — hold count = 2
lock.unlock(); // hold count = 1
lock.unlock(); // hold count = 0, lock released

Политика справедливого доступа

Создайте справедливую ReentrantLock, чтобы предоставлять доступ в порядке FIFO. Это предотвращает голодание потоков, но снижает пропускную способность:

// Unfair (default): no ordering guarantee, better throughput
ReentrantLock unfair = new ReentrantLock();

// Fair: threads acquire in arrival order
ReentrantLock fair = new ReentrantLock(true);

System.out.println(fair.isFair()); // true

Условные переменные

ReentrantLock предоставляет объекты условий — они гибче, чем wait()/notify():

ReentrantLock lock2 = new ReentrantLock();
Condition notEmpty = lock2.newCondition();
Queue<String> queue = new LinkedList<>();

// Producer
lock2.lock();
try {
    queue.offer("item");
    notEmpty.signal(); // wake one waiting thread
} finally { lock2.unlock(); }

// Consumer
lock2.lock();
try {
    while (queue.isEmpty()) notEmpty.await(); // wait & release lock
    System.out.println(queue.poll());
} finally { lock2.unlock(); }

Когда использовать ReentrantLock, а когда synchronized

Используйте synchronized, если:

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

Используйте ReentrantLock, если:

  • нужен tryLock() или tryLock() с ограничением времени;
  • нужен lockInterruptibly();
  • требуется справедливый порядок доступа;
  • нужно несколько условных переменных.

Счётчик блокировки и мониторинг

ReentrantLock предоставляет диагностические методы:

ReentrantLock lock3 = new ReentrantLock();
lock3.lock();
lock3.lock(); // reentrant — hold count 2

System.out.println(lock3.getHoldCount());    // 2
System.out.println(lock3.isHeldByCurrentThread()); // true
System.out.println(lock3.isLocked());        // true
System.out.println(lock3.getQueueLength());  // 0 (no waiting threads)

lock3.unlock();
lock3.unlock(); // fully released

Распространённая ошибка: освобождение незахваченной блокировки

Вызов unlock(), когда текущий поток не владеет блокировкой, вызывает IllegalMonitorStateException. Всегда объединяйте захват и освобождение блокировки в пару или используйте конструкцию try-finally:

ReentrantLock lock4 = new ReentrantLock();
try {
    // If lock() is never called (e.g., skipped by exception before this line)
    lock4.unlock(); // throws IllegalMonitorStateException
} catch (IllegalMonitorStateException e) {
    System.out.println("Must lock before unlock!");
}

Быстрая проверка

Какой метод ReentrantLock позволяет потоку захватить блокировку или сразу вернуть результат без блокировки, если блокировка недоступна?

Итоги: ReentrantLock и synchronized

Главное:

  • Оба механизма обеспечивают взаимное исключение и повторный захват
  • ReentrantLock добавляет tryLock, lockInterruptibly, справедливый доступ и условные переменные
  • Всегда освобождайте блокировку в блоке finally
  • synchronized проще — предпочитайте его, когда его возможностей достаточно
  • Используйте ReentrantLock в сложных сценариях: при ограничении времени ожидания, возможности прерывания и нескольких условных переменных

Часто задаваемые вопросы

Урок «ReentrantLock и synchronized» бесплатный?

Да — полный текст урока «ReentrantLock и synchronized» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «ReentrantLock и synchronized»?

Сравните возможности ReentrantLock (tryLock, lockInterruptibly, справедливость) с ключевым словом synchronized. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «ReentrantLock и synchronized»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. ReentrantLock и synchronized
  2. ReadWriteLock для сценариев чтения и записи
  3. Атомарные переменные: обновления без блокировок
  4. StampedLock и оптимистичное чтение
← Назад к Java Academy