StampedLock в Java | OTUS
⚡ Подписка на курсы OTUS!
Интенсивная прокачка навыков для IT-специалистов!
Подробнее

Курсы

Программирование
Backend-разработчик на PHP
-9%
Алгоритмы и структуры данных
-9%
Team Lead
-6%
Архитектура и шаблоны проектирования Разработчик IoT
-13%
C# Developer. Professional
-9%
HTML/CSS
-11%
C# ASP.NET Core разработчик
-5%
Kotlin Backend Developer
-8%
iOS Developer. Professional
-8%
Java Developer. Professional JavaScript Developer. Professional Базы данных Android Developer. Professional Framework Laravel Cloud Solution Architecture Highload Architect Reverse-Engineering. Professional Vue.js разработчик Agile Project Manager VOIP инженер Scala-разработчик Супер-практикум по использованию и настройке GIT Symfony Framework Java Developer. Basic Unity Game Developer. Professional Супер-интенсив Azure
Инфраструктура
Экспресс-курс «IaC Ansible»
-10%
Administrator Linux.Basic
-10%
Мониторинг и логирование: Zabbix, Prometheus, ELK
-10%
Экспресс-курс «CI/CD или Непрерывная поставка с Docker и Kubernetes»
-30%
Administrator Linux. Professional
-6%
Дизайн сетей ЦОД
-13%
NoSQL Основы Windows Server MS SQL Server Developer Инфраструктурная платформа на основе Kubernetes Cloud Solution Architecture Highload Architect Разработчик голосовых ассистентов и чат-ботов VOIP инженер Супер-практикум по работе с протоколом BGP Супер - интенсив по паттернам проектирования Супер - интенсив по Kubernetes Супер-интенсив "Tarantool"
Специализации Курсы в разработке Подготовительные курсы
+7 499 938-92-02

StampedLock в Java

Java_Deep_16.12-5020-8569f2.png

В Java 8 в пакете java.util.concurrent.locks появился интересный класс – StampedLock. Этот класс в ряде случаев приносит исключительную пользу, однако не все даже опытные программисты про него знают. Сегодня мы немного подправим эту досадную ситуацию.

Из названия очевидно, что класс StampedLock реализует механизм блокировок и является функциональным аналогом хорошо известным механизмам synchronized и ReentrantLock.

Оптимистичная блокировка

У StampedLock есть ряд интересных особенностей, сегодня мы рассмотрим одну из них – «оптимистичная блокировка». «Оптимистичная блокировка» – широко известный принцип в организации многопользовательского доступа к базам данных. Принцип работы очень простой – читаем данные, надеясь, что их никто не успел изменить. Если всё же кто-то поменял, то читаем ещё раз или выставляем блокировку (если уровень оптимизма уменьшился и читаем ещё раз.

Рассмотрим пример

Есть общая переменная. Один поток эту переменную меняет, два другие читают. Причём поток-писатель делает своё дело долго, но относительно редко. А читатели читают часто, но быстро. Как бы мы реализовали эту схему «традиционными средствами»? Писатель и читатели блокировали бы общую переменную для выполнения своих действий. При этом они мешали бы друг другу и общая производительность системы снижалась бы. Мы могли бы использовать раздельные блокировки – на чтение и на запись. Стало бы лучше, но не сильно. Потоки всё равно «мешали» бы друг другу. Т. к. писатель один и работает редко, мы можем использовать «оптимистичную блокировку» в надежде на то, что писатель в большинстве случаев не успевает изменить данные.

Как это выглядит в коде:

    private void counterWriter() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                long stamp = sl.writeLock(); // выставляем блокировку на запись
                try {
                    long tmp = counter;
                    System.out.println("start counter modification:" + tmp);
                    Thread.sleep(10_000);
                    tmp++;
                    counter = tmp; // изменяем общую переменную.
                    System.out.println("end counter modification:" + tmp);
                } finally {
                    sl.unlockWrite(stamp); //снимаем блокировку на запись
                }
                Thread.sleep(30_000);
            }
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
        }
    }

Как выглядит читатель:

    private void counterReader(int id) {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                long stamp = sl.tryOptimisticRead(); // берем метку состояния
                long tmp = counter; // читаем значение общей переменной
                if (!sl.validate(stamp)) { // проверяем метку состояния, 
                    System.out.println("    id:" + id + " protected value has been changed");
                    stamp = sl.readLock(); // если состояние изменилось, ставим блокировку
                    System.out.println("    id:" + id + " new readLock");
                    try {
                        tmp = counter; // читаем данные под блокировкой
                    } finally {
                        sl.unlockRead(stamp); // снимаем блокировку
                    }
                }
                System.out.println("    id:" + id + " current value:" + tmp);
                Thread.sleep(1_000);
            }
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
        }
    }

Логика работы основана на метке – значении, которое отражает состояние данных, которые мы защищаем критической секцией.

Если между моментом «фиксации состояния» (long stamp = sl.tryOptimisticRead();) и проверкой (sl.validate(stamp))) метка изменилась, значит кто-то изменил общее состояние, и данные надо перечитать.

Почему этот подход более эффективен, чем например synchronized? Т. к. данные меняются редко, то нет необходимости на каждое чтение выставлять блокировку, которая является весьма ресурсозатратной операцией.

Полный пример находится по ссылке.

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться