Блокировки. ReentrantLock

Последнее обновление: 28.04.2018

Для управления доступом к общему ресурсу в качестве альтернативы оператору synchronized мы можем использовать блокировки. Функциональность блокировок заключена в пакете java.util.concurrent.locks.

Вначале поток пытается получить доступ к общему ресурсу. Если он свободен, то на него накладывает блокировку. После завершения работы блокировка с общего ресурса снимается. Если же ресурс не свободен и на него уже наложена блокировка, то поток ожидает, пока эта блокировка не будет снята.

Классы блокировок реализуют интерфейс Lock, который определяет следующие методы:

  • void lock(): ожидает, пока не будет получена блокировка

  • void lockInterruptibly() throws InterruptedException: ожидает, пока не будет получена блокировка, если поток не прерван

  • boolean tryLock(): пытается получить блокировку, если блокировка получена, то возвращает true. Если блокировка не получена, то возвращает false. В отличие от метода lock() не ожидает получения блокировки, если она недоступна

  • void unlock(): снимает блокировку

  • Condition newCondition(): возвращает объект Condition, который связан с текущей блокировкой

Организация блокировки в общем случае довольно проста: для получения блокировки вызывается метод lock(), а после окончания работы с общими ресурсами вызывается метод unlock(), который снимает блокировку.

Объект Condition позволяет управлять блокировкой.

Как правило, для работы с блокировками используется класс ReentrantLock из пакета java.util.concurrent.locks. Данный класс реализует интерфейс Lock.

Для примера возьмем код из темы про оператор synchronized и перепишем данный код с использованием заглушки ReentrantLock:

import java.util.concurrent.locks.ReentrantLock;

public class Program {
 
    public static void main(String[] args) {
         
        CommonResource commonResource= new CommonResource();
        ReentrantLock locker = new ReentrantLock(); // создаем заглушку
        for (int i = 1; i < 6; i++){
             
            Thread t = new Thread(new CountThread(commonResource, locker));
            t.setName("Thread "+ i);
            t.start();
        }
    }
}
 
class CommonResource{
     
    int x=0;
}
 
class CountThread implements Runnable{
 
    CommonResource res;
    ReentrantLock locker;
    CountThread(CommonResource res, ReentrantLock lock){
        this.res=res;
        locker = lock;
    }
    public void run(){
        
		locker.lock(); // устанавливаем блокировку
        try{
            res.x=1;
            for (int i = 1; i < 5; i++){
                System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x);
                res.x++;
                Thread.sleep(100);
            }
        }
        catch(InterruptedException e){
            System.out.println(e.getMessage());
        }
        finally{
            locker.unlock(); // снимаем блокировку
        }
    }
}

Здесь также используется общий ресурс CommonResource, для управления которым создается пять потоков. На входе в критическую секцию устанавливается заглушка:

locker.lock();

После этого только один поток имеет доступ к критической секции, а остальные потоки ожидают снятия блокировки. В блоке finally после всей окончания основной работы потока эта блокировка снимается. Причем делается это обязательно в блоке finally, так как в случае возникновения ошибки все остальные потоки окажутся заблокированными.

В итоге мы получим вывод, аналогичный тому, который был в случае с оператором synchronized:

Thread 4 1 
Thread 4 2 
Thread 4 3 
Thread 4 4 
Thread 3 1 
Thread 3 2 
Thread 3 3 
Thread 3 4 
Thread 2 1 
Thread 2 2 
Thread 2 3 
Thread 2 4 
Thread 1 1 
Thread 1 2 
Thread 1 3 
Thread 1 4 
Thread 5 1 
Thread 5 2 
Thread 5 3 
Thread 5 4
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850