Статьи

Java 8 StampedLocks против ReadWriteLocks и Synchronized

Blog_key_

Синхронизированные разделы похожи на посещение ваших родителей в законе. Вы хотите быть там как можно меньше. Когда дело доходит до блокировки, правила одинаковы — вы хотите потратить наименьшее количество времени на получение блокировки и в критической секции, чтобы предотвратить образование узких мест.

Основным языком для блокировки всегда было ключевое слово synchronized для методов и дискретных блоков. Это ключевое слово действительно встроено в JSM HotSpot. Каждый объект, который мы размещаем в нашем коде, будь то String, Array или полноценный JSON-документ, имеет встроенные возможности блокировки прямо в его заголовке на собственном уровне GC. То же самое касается JIT-компилятора, который компилирует и повторно компилирует байт-код в зависимости от конкретного состояния и уровней конкуренции для конкретной блокировки.

Проблема с синхронизированными блоками в том, что они все или ничего — в критической секции не может быть более одного потока. Это особенно обидно в сценариях «потребитель / производитель», где некоторые потоки пытаются редактировать только некоторые данные, в то время как другие только пытаются их прочитать, и у них хорошо работает общий доступ.

ReadWriteLocks должны были стать идеальным решением для этого. Вы можете указать, какие потоки блокируют всех остальных (писателей), а какие лучше всего подходят другим для потребления контента (читателей). Счастливый конец? Не боюсь.

В отличие от синхронизированных блоков, блокировки RW не встроены в JVM и имеют те же возможности, что и простой смертный код. Тем не менее, для реализации идиомы блокировки вам нужно дать указание процессору выполнить определенные операции атомарно или в определенном порядке, чтобы избежать условий гонки. Это традиционно делается через магическую портальную дыру в JVM — небезопасный класс. Блокировки RW используют операции сравнения и замены (CAS) для установки значений непосредственно в память как часть их алгоритма организации потоков.

Несмотря на это, RWLocks просто недостаточно быстрые, и порой оказываются действительно чертовски медленными , вплоть до того, что с ними не стоит возиться. Однако помощь уже в пути: хорошие люди из JDK не сдаются , и теперь вернулись с новым StampedLock . Эта блокировка RW использует новый набор алгоритмов и функций ограждения памяти, добавленных в Java 8 JDK, чтобы помочь сделать эту блокировку более быстрой и надежной.

Это выполняет свое обещание? Давайте посмотрим.

Используя замок. На первый взгляд, StampedLocks более сложны в использовании. Они используют концепцию марок, которые представляют собой длинные значения, которые служат в качестве билетов, используемых любой операцией блокировки / разблокировки. Это означает, что для разблокировки операции R / W вам необходимо передать ей соответствующую отметку блокировки. Передавайте неправильный штамп, и вы рискуете получить исключение или, что еще хуже, неожиданное поведение.

Еще одна ключевая вещь, о которой стоит помнить, это то, что в отличие от RWLocks, StampedLocks не реентерабельны . Таким образом, хотя они могут быть быстрее, у них есть и обратная сторона: теперь потоки могут взаимоблокировать себя. На практике это означает, что более чем когда-либо вы должны следить за тем, чтобы блокировки и штампы не выходили за пределы входящих в них блоков кода.

01
02
03
04
05
06
07
08
09
10
11
long stamp = lock.writeLock();  //blocking lock, returns a stamp
 
try {
 
  write(stamp); // this is a bad move, you’re letting the stamp escape
}
 
finally {
 
  lock.unlock(stamp);// release the lock in the same block - way better
}

Еще одна любимая мозоль, которую я имею с этим дизайном, состоит в том, что марки служат длинными ценностями, которые на самом деле ничего не значат для вас. Я бы предпочел, чтобы операции блокировки возвращали объект, который описывает штамп — его тип (R / W), время блокировки, поток владельца и т. Д. Это облегчило бы отладку и ведение журнала. Это, вероятно, сделано намеренно и предназначено для того, чтобы не дать разработчикам передавать марки между различными частями кода, а также сэкономить на стоимости размещения объекта.

Оптимистичная блокировка . Наиболее важной частью с точки зрения новых возможностей для этой блокировки является новый режим оптимистической блокировки. Исследования и практический опыт показывают, что операции чтения по большей части не связаны с операциями записи. В результате, получение полной блокировки чтения может оказаться излишним. Лучший подход может состоять в том, чтобы продолжить и выполнить чтение, и в конце увидеть, действительно ли значение было изменено за это время. Если это так, вы можете повторить чтение или перейти на более тяжелую блокировку.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
long stamp = lock.tryOptimisticRead(); // non blocking
 
read();
 
if(!lock.validate(stamp)){ // if a write occurred, try again with a read lock
 
  long stamp = lock.readLock();
 
  try {
 
    read();
  }
  finally {
    
    lock.unlock(stamp);
  }
}

Одна из самых больших проблем при выборе блокировки заключается в том, что ее фактическое поведение в производственной среде будет зависеть от состояния приложения. Это означает, что выбор идиомы блокировки не может быть сделан в вакууме и должен учитывать реальные условия, при которых будет выполняться код.

Количество одновременных потоков чтения и записи изменит то, какую блокировку вы должны использовать — синхронизированную секцию или блокировку RW. Это становится сложнее, так как эти числа могут меняться в течение жизненного цикла JVM, в зависимости от состояния приложения и конкуренции за потоки.

Чтобы проиллюстрировать это, я протестировал четыре режима блокировки — синхронизированную, RW-блокировку, Stamped RW-блокировку и RW-оптимистическую блокировку при различных уровнях конкуренции и комбинациях потоков R / W. Потоки чтения будут использовать значение счетчика, а потоки записи будут увеличивать его с 0 до 1M.

5 читателей против 5 писателей. Собирая пять параллельных читателей и пять писательских потоков, мы видим, что штампованный замок светит, работая намного лучше, чем синхронизированный, с коэффициентом 3X. Блокировка RW также работала хорошо. Странно то, что оптимистическая блокировка, которая на первый взгляд должна быть самой быстрой, на самом деле самая медленная .

04

10 читателей против 10 писателей. Затем я увеличил количество споров до десяти писателей и десяти читателей. Здесь вещи начинают существенно меняться. Блокировка RW теперь на порядок медленнее, чем штампованные и синхронизированные блокировки, которые работают на одном уровне. Обратите внимание, что оптимистическая блокировка на удивление все еще медленнее блокируется RW-блокировкой.

01

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

02

19 читателей против 1 писателя: наконец, я посмотрел, как работает один авторский поток против девятнадцати читателей. Обратите внимание, что результаты намного медленнее, так как выполнение одного потока занимает больше времени. Здесь мы получаем довольно интересные результаты. Не удивительно, что блокировка RW занимает бесконечность, чтобы закончить. Тем не менее, штампованная блокировка работает не намного лучше … Оптимистическая блокировка — явный победитель, опережая блокировку RW в 100 раз. Однако имейте в виду, что этот режим блокировки может дать вам сбой, поскольку в это время может произойти запись. Синхронизированный, наш старый верующий, продолжает приносить солидные результаты.

03

Полные результаты можно найти здесь … Аппаратное обеспечение: MBP Quad Core i7.

Код теста можно найти здесь .

Выводы

Кажется, что в среднем лучшая производительность в целом все еще обеспечивается встроенной синхронизированной блокировкой. Тем не менее, дело здесь не в том, чтобы сказать, что он будет работать лучше во всех ситуациях. Это в основном показывает, что ваш выбор идиом блокировки должен быть сделан на основе тестирования как ожидаемого уровня конкуренции, так и разделения потоков чтения и записи до того, как вы приступите к написанию кода. В противном случае вы рискуете получить серьезную производственную отладочную боль .

Дополнительное чтение о StampedLocks здесь .

Вопросы, комментарии, заметки о бенчмарке? Дай мне знать!

Ссылка: Java 8 StampedLocks против ReadWriteLocks и Synchronized от нашего партнера JCG Тала Вейса из блога Takipi .