0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Теги: java, stampedlock, механизм блокировок, оптимистичная блокировка</p>
1
<p>Теги: java, stampedlock, механизм блокировок, оптимистичная блокировка</p>
2
<p>В<strong>Java 8</strong>в пакете<strong>java.util.concurrent.locks</strong>появился интересный класс -<strong>StampedLock</strong>. Этот класс в ряде случаев приносит исключительную пользу, однако не все даже опытные программисты про него знают. Сегодня мы немного подправим эту досадную ситуацию.</p>
2
<p>В<strong>Java 8</strong>в пакете<strong>java.util.concurrent.locks</strong>появился интересный класс -<strong>StampedLock</strong>. Этот класс в ряде случаев приносит исключительную пользу, однако не все даже опытные программисты про него знают. Сегодня мы немного подправим эту досадную ситуацию.</p>
3
<p>Из названия очевидно, что класс StampedLock реализует механизм блокировок и является функциональным аналогом хорошо известным механизмам<strong>synchronized</strong>и<strong>ReentrantLock</strong>.</p>
3
<p>Из названия очевидно, что класс StampedLock реализует механизм блокировок и является функциональным аналогом хорошо известным механизмам<strong>synchronized</strong>и<strong>ReentrantLock</strong>.</p>
4
<h2>Оптимистичная блокировка</h2>
4
<h2>Оптимистичная блокировка</h2>
5
<p>У StampedLock есть ряд интересных особенностей, сегодня мы рассмотрим одну из них - "оптимистичная блокировка". "Оптимистичная блокировка" - широко известный принцип в организации многопользовательского доступа к базам данных. Принцип работы очень простой - читаем данные, надеясь, что их никто не успел изменить. Если всё же кто-то поменял, то читаем ещё раз или выставляем блокировку (если уровень оптимизма уменьшился и читаем ещё раз.</p>
5
<p>У StampedLock есть ряд интересных особенностей, сегодня мы рассмотрим одну из них - "оптимистичная блокировка". "Оптимистичная блокировка" - широко известный принцип в организации многопользовательского доступа к базам данных. Принцип работы очень простой - читаем данные, надеясь, что их никто не успел изменить. Если всё же кто-то поменял, то читаем ещё раз или выставляем блокировку (если уровень оптимизма уменьшился и читаем ещё раз.</p>
6
<h2>Рассмотрим пример</h2>
6
<h2>Рассмотрим пример</h2>
7
<p>Есть общая переменная. Один поток эту переменную меняет, два другие читают. Причём<strong>поток-писатель</strong>делает своё дело долго, но относительно редко. А читатели читают часто, но быстро. Как бы мы реализовали эту схему "традиционными средствами"? Писатель и читатели блокировали бы общую переменную для выполнения своих действий. При этом они мешали бы друг другу и<strong>общая производительность системы снижалась бы</strong>. Мы могли бы использовать раздельные блокировки - на чтение и на запись. Стало бы лучше, но не сильно. Потоки всё равно "мешали" бы друг другу. Т. к. писатель один и работает редко, мы можем использовать "оптимистичную блокировку" в надежде на то, что писатель в большинстве случаев не успевает изменить данные.</p>
7
<p>Есть общая переменная. Один поток эту переменную меняет, два другие читают. Причём<strong>поток-писатель</strong>делает своё дело долго, но относительно редко. А читатели читают часто, но быстро. Как бы мы реализовали эту схему "традиционными средствами"? Писатель и читатели блокировали бы общую переменную для выполнения своих действий. При этом они мешали бы друг другу и<strong>общая производительность системы снижалась бы</strong>. Мы могли бы использовать раздельные блокировки - на чтение и на запись. Стало бы лучше, но не сильно. Потоки всё равно "мешали" бы друг другу. Т. к. писатель один и работает редко, мы можем использовать "оптимистичную блокировку" в надежде на то, что писатель в большинстве случаев не успевает изменить данные.</p>
8
<p>Как это выглядит в коде:</p>
8
<p>Как это выглядит в коде:</p>
9
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(); } }<p>Как выглядит читатель:</p>
9
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(); } }<p>Как выглядит читатель:</p>
10
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(); } }<p>Логика работы основана на метке - значении, которое отражает состояние данных, которые мы защищаем критической секцией.</p>
10
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(); } }<p>Логика работы основана на метке - значении, которое отражает состояние данных, которые мы защищаем критической секцией.</p>
11
<p>Если между моментом "фиксации состояния" (long stamp = sl.tryOptimisticRead();) и проверкой (sl.validate(stamp))) метка изменилась, значит кто-то изменил общее состояние, и данные надо перечитать.</p>
11
<p>Если между моментом "фиксации состояния" (long stamp = sl.tryOptimisticRead();) и проверкой (sl.validate(stamp))) метка изменилась, значит кто-то изменил общее состояние, и данные надо перечитать.</p>
12
<p>Почему этот подход более эффективен, чем например<strong>synchronized</strong>? Т. к. данные меняются редко, то нет необходимости на каждое чтение выставлять блокировку, которая является весьма ресурсозатратной операцией.</p>
12
<p>Почему этот подход более эффективен, чем например<strong>synchronized</strong>? Т. к. данные меняются редко, то нет необходимости на каждое чтение выставлять блокировку, которая является весьма ресурсозатратной операцией.</p>
13
<p>Полный пример находится по<a>ссылке</a>.</p>
13
<p>Полный пример находится по<a>ссылке</a>.</p>
14
14