Блокування. ReentrantLock. Умови блокування
Блокування. ReentrantLock. Умови блокування – курс джава
У Java для контролю доступу до спільних ресурсів у багатопоточних програмах використовуються різні механізми блокування. Одним із найбільш потужних і гнучких засобів блокування є клас **`ReentrantLock`**, що входить до пакету `java.util.concurrent.locks`.
Блокування (Locks)
Блокування дозволяють синхронізувати доступ до спільних ресурсів, коли кілька потоків одночасно намагаються змінювати ці ресурси. Хоча вбудований механізм `synchronized` також забезпечує блокування, `ReentrantLock` дає більше можливостей і гнучкості, таких як:
- Спроба захопити блокування з тайм-аутом.
- Перевірка стану блокування.
- Можливість розблокування в іншому місці (тобто в іншому методі).
- Використання умов блокування (condition), що дозволяє потокам чекати та пробуджуватися в більш контрольованому порядку.
Клас ReentrantLock
Клас `ReentrantLock` дозволяє створити реентрантне блокування, тобто потік, який вже захопив блокування, може повторно захопити його без блокування самого себе.
Основні методи ReentrantLock:
- `lock()`: Захоплює блокування. Якщо блокування вже зайняте іншим потоком, потік, який викликає цей метод, блокується, поки блокування не буде звільнено.
- `unlock()`: Звільняє блокування, яке було захоплене потоком.
- `tryLock()`: Намагається захопити блокування без очікування. Повертає `true`, якщо блокування було захоплено, інакше — `false`.
- `tryLock(long time, TimeUnit unit)`: Намагається захопити блокування протягом заданого часу, після чого, якщо не вдалося, повертає `false`.
- `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:
- `await()`: Потік чекає, поки його не пробудять або не виконається умова.
- `signal()`: Пробуджує один потік, який чекає на умову.
- `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) дають можливість створювати складніші механізми взаємодії між потоками, забезпечуючи потоки можливістю чекати на певні умови та пробуджуватися при їх виконанні.