Статья подготовлена для студентов курса «Разработчик Java» в образовательном проекте OTUS.
В Java 8 в пакете java.util.concurrent.locks появился интересный класс – StampedLock. Этот класс в ряде случаев приносит исключительную пользу, однако не все даже опытные программисты про него знают. Сегодня мы немного подправим эту досадную ситуацию.
Из названия очевидно, что класс StampedLock реализует механизм блокировок и является функциональным аналогом хорошо известным механизмам synchronized и ReentrantLock.
Оптимистичная блокировка
У StampedLock есть ряд интересных особенностей, сегодня мы рассмотрим одну из них – «оптимистичная блокировка». «Оптимистичная блокировка» – широко известный принцип в организации многопользовательского доступа к базам данных. Принцип работы очень простой – читаем данные, надеясь, что их никто не успел изменить. Если всё же кто-то поменял, то читаем ещё раз или выставляем блокировку (если уровень оптимизма уменьшился и читаем ещё раз.
Рассмотрим пример
Есть общая переменная. Один поток эту переменную меняет, два другие читают. Причём поток-писатель делает своё дело долго, но относительно редко. А читатели читают часто, но быстро. Как бы мы реализовали эту схему «традиционными средствами»? Писатель и читатели блокировали бы общую переменную для выполнения своих действий. При этом они мешали бы друг другу и общая производительность системы снижалась бы. Мы могли бы использовать раздельные блокировки – на чтение и на запись. Стало бы лучше, но не сильно. Потоки всё равно «мешали» бы друг другу. Т. к. писатель один и работает редко, мы можем использовать «оптимистичную блокировку» в надежде на то, что писатель в большинстве случаев не успевает изменить данные.
Как это выглядит в коде:
Как выглядит читатель:
Логика работы основана на метке – значении, которое отражает состояние данных, которые мы защищаем критической секцией.
Если между моментом «фиксации состояния» (long stamp = sl.tryOptimisticRead();) и проверкой (sl.validate(stamp))) метка изменилась, значит кто-то изменил общее состояние, и данные надо перечитать.
Почему этот подход более эффективен, чем например synchronized? Т. к. данные меняются редко, то нет необходимости на каждое чтение выставлять блокировку, которая является весьма ресурсозатратной операцией.
Полный пример находится по ссылке.
Изучайте программу курса «Разработчик Java» , проходите вступительное тестирование и присоединяйтесь к новой группе по специальной новогодней цене!