У Java управління одночасним доступом до спільних ресурсів є надзвичайно важливим для забезпечення цілісності даних та запобігання проблемам, таким як умови гонки. Пакет java.util.concurrent.locks забезпечує різні механізми блокування для досягнення цієї мети, і одним з найпоширеніших є ReentrantLock.
Що таке ReentrantLock?
ReentrantLock - це примітив синхронізації, який пропонує більше гнучкості та функцій порівняно з традиційними блоками synchronized. Він є частиною пакету java.util.concurrent.locks і надає більш розширений API для операцій блокування та розблокування. Як випливає з назви, повторюваний блок (reentrant lock) дозволяє потоку, який утримує блокування, повторно заходити в блокування кілька разів без виникнення deadlock.
Основні характеристики ReentrantLock
- Повторюваність (Reentrancy): Замок може бути захоплений одним і тим же потоком кілька разів. Це означає, що якщо потік вже утримує замок і намагається захопити його знову, він успішно захопить його без блокування.
- Справедливість (Fairness):
ReentrantLockможе бути створений як справедливий або несправедливий. Справедливий замок гарантує, що потік, який найдовше чекає, отримує доступ до замка першим, тоді як несправедливий замок може дозволити потокам “підрізати чергу”. - Умовні змінні (Condition Variables):
ReentrantLockнадає умовні змінні через методnewCondition(), дозволяючи потокам чекати на виконання певних умов. - Переривання при захопленні замка (Interruptible Lock Acquisition): Потоки можуть бути перервані під час очікування на захоплення замка.
- Спроба захоплення замка (Try Lock): Метод
tryLock()дозволяє потоку спробувати захопити замок без блокування.
Основне використання ReentrantLock
Ось простий приклад, щоб продемонструвати використання ReentrantLock:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock(); // Захоплюємо замок
try {
counter++;
} finally {
lock.unlock(); // Завжди розблоковуємо замок у блоці finally
}
}
public int getCounter() {
lock.lock(); // Захоплюємо замок
try {
return counter;
} finally {
lock.unlock(); // Завжди розблоковуємо замок у блоці finally
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockExample example = new ReentrantLockExample();
// Створюємо 1000 потоків, які інкрементують лічильник
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(example::increment);
threads[i].start();
}
// Чекаємо завершення всіх потоків
for (Thread thread : threads) {
thread.join();
}
System.out.println("Фінальне значення лічильника: " + example.getCounter());
}
}
У цьому прикладі:
ReentrantLockвикористовується для забезпечення потокобезпеки методівincrementтаgetCounter.- Метод
lock.lock()захоплює замок, аlock.unlock()розблоковує його. Виклик unlock розташований у блоціfinally, щоб гарантувати розблокування навіть у разі виникнення виключення.
Розширені можливості
- Справедливий замок: Щоб створити справедливий ReentrantLock, передайте true до конструктора:Це гарантує, що потік, який найдовше чекає, отримає замок першим.
Lock fairLock = new ReentrantLock(true); - Спроба захоплення замка: Щоб уникнути блокування, використовуйте tryLock():
if (lock.tryLock()) { try { // Виконання операцій під захистом замка } finally { lock.unlock(); } } else { // Обробка випадку, коли замок не був захоплений } - Умовні змінні: Використовуйте умовні змінні для складнішої координації потоків:
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void awaitCondition() throws InterruptedException { lock.lock(); try { condition.await(); // Очікування до сигналу } finally { lock.unlock(); } } public void signalCondition() { lock.lock(); try { condition.signal(); // Сигнал одному потоку, який чекає } finally { lock.unlock(); } }
Висновок
ReentrantLock забезпечує потужний та гнучкий механізм для синхронізації потоків у Java.
Він розширює можливості традиційних блоків synchronized, пропонуючи такі функції, як повторюваність, справедливість, переривання при захопленні замка, несинхронні спроби та умовні змінні.
Розуміння та ефективне використання ReentrantLock може призвести до більш надійних та підтримуваних багатопоточних додатків.