Блокування. 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) дають можливість створювати складніші механізми взаємодії між потоками, забезпечуючи потоки можливістю чекати на певні умови та пробуджуватися при їх виконанні.

 

Insert math as
Block
Inline
Additional settings
Formula color
Text color
#333333
Type math using LaTeX
Preview
\({}\)
Nothing to preview
Insert