Блокування. ReentrantLock. Умови блокування

Прокрутити вниз

Блокування. ReentrantLock. Умови блокування – курс джава

У Java для контролю доступу до спільних ресурсів у багатопоточних програмах використовуються різні механізми блокування. Одним із найбільш потужних і гнучких засобів блокування є клас **`ReentrantLock`**, що входить до пакету `java.util.concurrent.locks`.

Блокування (Locks)

Блокування дозволяють синхронізувати доступ до спільних ресурсів, коли кілька потоків одночасно намагаються змінювати ці ресурси. Хоча вбудований механізм `synchronized` також забезпечує блокування, `ReentrantLock` дає більше можливостей і гнучкості, таких як:

  • Спроба захопити блокування з тайм-аутом.
  • Перевірка стану блокування.
  • Можливість розблокування в іншому місці (тобто в іншому методі).
  • Використання умов блокування (condition), що дозволяє потокам чекати та пробуджуватися в більш контрольованому порядку.

Клас ReentrantLock

Клас `ReentrantLock` дозволяє створити реентрантне блокування, тобто потік, який вже захопив блокування, може повторно захопити його без блокування самого себе.

Основні методи ReentrantLock:

  1. `lock()`: Захоплює блокування. Якщо блокування вже зайняте іншим потоком, потік, який викликає цей метод, блокується, поки блокування не буде звільнено.
  2. `unlock()`: Звільняє блокування, яке було захоплене потоком.
  3. `tryLock()`: Намагається захопити блокування без очікування. Повертає `true`, якщо блокування було захоплено, інакше — `false`.
  4. `tryLock(long time, TimeUnit unit)`: Намагається захопити блокування протягом заданого часу, після чого, якщо не вдалося, повертає `false`.
  5. `lockInterruptibly()`: Захоплює блокування, але дозволяє переривання потоку, який чекає на блокування.

Приклад використання ReentrantLock:

import java.util.concurrent.locks.ReentrantLock;

class SharedResource {
private ReentrantLock lock = new ReentrantLock();
private int counter = 0;

public void increment() {
lock.lock(); // Захоплюємо блокування
try {
counter++;
System.out.println(Thread.currentThread().getName() + ” збільшив лічильник: ” + counter);
} finally {
lock.unlock(); // Завжди розблоковуємо в блоці finally
}
}
}

public class Main {
public static void main(String[] args) {
SharedResource sharedResource = new SharedResource();

Thread t1 = new Thread(sharedResource::increment, “Потік 1”);
Thread t2 = new Thread(sharedResource::increment, “Потік 2”);

t1.start();
t2.start();
}
}

Виведення:

Потік 1 збільшив лічильник: 1
Потік 2 збільшив лічильник: 2

У цьому прикладі використовується ReentrantLock для захисту критичної секції, в якій відбувається збільшення лічильника.

Умови блокування (Conditions)

У класі `ReentrantLock` є можливість створити об’єкти Condition для управління потоками, які чекають на певну умову для продовження виконання.

Умови блокування схожі на механізм `wait()`, `notify()` і `notifyAll()`, які використовуються разом із `synchronized`. Але об’єкти Condition дають більше гнучкості та можуть створювати кілька умов для одного блокування.

Основні методи Condition:

  1. `await()`: Потік чекає, поки його не пробудять або не виконається умова.
  2. `signal()`: Пробуджує один потік, який чекає на умову.
  3. `signalAll()`: Пробуджує всі потоки, що чекають на умову.

Приклад використання Condition:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class SharedResource {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean ready = false;

public void waitForCondition() throws InterruptedException {
lock.lock();
try {
while (!ready) {
System.out.println(Thread.currentThread().getName() + ” чекає…”);
condition.await(); // Чекаємо, поки стан не зміниться
}
System.out.println(Thread.currentThread().getName() + ” пробуджений!”);
} finally {
lock.unlock();
}
}

public void signalCondition() {
lock.lock();
try {
ready = true;
condition.signal(); // Пробуджуємо один з потоків
} finally {
lock.unlock();
}
}
}

public class Main {
public static void main(String[] args) throws InterruptedException {
SharedResource sharedResource = new SharedResource();

Thread waiter = new Thread(() -> {
try {
sharedResource.waitForCondition();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, “Потік-очікувач”);

Thread notifier = new Thread(sharedResource::signalCondition, “Потік-повідомлювач”);

waiter.start();
Thread.sleep(1000); // Невелика затримка перед пробудженням
notifier.start();
}
}

Виведення:

Потік-очікувач чекає…
Потік-очікувач пробуджений!

У цьому прикладі потік `waiter` чекає, поки інший потік не змінить умову і не викличе `signal()` для його пробудження. Це подібно до використання `wait()` і `notify()` з `synchronized`, але з більшою гнучкістю.

Відмінності між `synchronized` і ReentrantLock:

`synchronized`:

  • Простий у використанні.
  • Автоматично звільняє блокування після завершення блоку коду.
  • Обмежений в можливостях (немає спроб захоплення блокування або чекання з тайм-аутом).

`ReentrantLock`:

  • – Більше можливостей, таких як спроба захопити блокування без блокування (метод `tryLock()`) або з можливістю переривання (`lockInterruptibly()`).
  • Необхідність явного виклику методу `unlock()`.
  • Підтримує Condition, що дає більшу гнучкість у синхронізації потоків.

Висновок

  •  ReentrantLock надає розширені можливості для блокування порівняно з вбудованим механізмом `synchronized`, такі як контроль над спробою захоплення блокування, підтримка умов блокування та більше гнучкості у керуванні потоками.
  • Умови блокування (Conditions) дають можливість створювати складніші механізми взаємодії між потоками, забезпечуючи потоки можливістю чекати на певні умови та пробуджуватися при їх виконанні.