Будь то интервью или реальная работа, Redis является неизбежным техническим моментом. На мой взгляд, MySQL и Redis — это две линейки для измерения «успешности» программиста. Если он может умело использовать MySQL и Redis, чтобы в полной мере использовать существующие ресурсы для удовлетворения текущих потребностей, это показывает, что он вырос.
В этой статье будет рассказано о синхронизированной и ReentrantLock JVM для распределенной блокировки Redis.Это не связано с конкретной реализацией кода, но представляет идеи и общие принципы простым способом, который очень дружелюбен к Xiaobai~
Блокировка JVM
Так называемая блокировка JVM на самом деле относится к блокировке, реализуемой ключевым словом synchronized или ReentrantLock. Причина, по которой они вместе называются блокировками JVM, заключается в том, что наши проекты фактически выполняются на JVM. Теоретически, после запуска каждого проекта ему соответствует кусок памяти JVM, и жизнь и смерть последующих данных времени выполнения — все на этом кусочке земли.
Что такое замок и как его заблокировать?
Разбираемся в происхождении названия «замок JVM», поговорим о том, что такое «блокировка» и как «блокировать».
Иногда трудно сформулировать, что это такое, но легко объяснить, что оно может делать, и то же самое касается блокировок JVM. Появление блокировок JVM должно решить проблему безопасности потоков. Так называемую проблему безопасности потоков можно просто понимать как несогласованность данных (несоответствие ожиданиям).
Когда могут возникнуть проблемы безопасности потоков?
Проблемы с потокобезопасностью могут возникнуть только при одновременном выполнении следующих трех условий:
- многопоточная среда
- есть общие данные
- Несколько операторов работают с общими данными/один оператор сам по себе не является атомарной операцией (например, i++ — это один оператор, но это не атомарная операция)
Например, потоки A и B одновременно выполняют +1 операцию над int count (начальное значение предполагается равным 1), а конечный результат двух операций может быть 2 вместо 3 с определенной вероятностью.
Так почему же блокировка решает эту проблему?
Если не учитывать неясные термины, такие как атомарность и барьеры памяти, основной причиной, по которой блокировка может обеспечить безопасность потоков, является «взаимное исключение». Взаимное исключение означает взаимное исключение в буквальном смысле. Кого здесь имеет в виду «взаимно»? Находится между несколькими потоками!
Как добиться взаимного исключения между несколькими потоками?
Ввести «посредника».
Обратите внимание, это очень простая и отличная идея. В мире программирования есть бесчисленное множество случаев, когда проблемы окончательно решаются путем введения «посредников», включая Spring и MQ, но не ограничиваясь ими. Среди кодовых фермеров даже есть поговорка: нет проблемы, которую нельзя было бы решить, внедрив средний слой.
Блокировка JVM на самом деле является «посредником» между потоками и друг другом,Несколько потоков должны запросить согласие «посредника» перед операцией с заблокированными данными:
Роль блокировки здесь фактически является привратником, единственной записью доступа, и все потоки должны пройти через эту пытку. В JDK есть два наиболее распространенных механизма реализации блокировок, а именно две фракции:
- синхронизированное ключевое слово
- AQS
Лично я думаю, что ключевое слово synchronized сложнее для понимания, чем AQS, но исходный код AQS более абстрактен. Вот краткое введение в структуру памяти объектов Java и принцип реализации ключевого слова synchronized.
Структура памяти объектов Java
Чтобы понять ключевое слово synchronized, вы должны сначала узнать структуру памяти объектов Java. Опять же, это объект Javaструктура памяти.
Его существование, кажется, ставит перед нами вопрос: если у нас есть возможность разобрать объект Java, что мы сможем увидеть?
Справа вверху нарисованы два объекта, просто посмотрите на один из них. Мы можем заметить, что структура памяти объектов Java грубо разделена на несколько блоков:
- Отметить Word (связанный с замком)
- Указатель метаданных (указатель класса, указывающий на класс, к которому принадлежит текущий экземпляр)
- Данные экземпляра (данные экземпляра, мы обычно видим только этот кусок)
- Выравнивание (заполнение, связанное с выравниванием памяти)
Если вы раньше не знали о структуре памяти объектов Java, вы можете быть удивлены: Боже, я думал, что у объектов Java есть только свойства и методы!
Да, мы лучше всего знали часть данных экземпляра и думали, что это единственная часть. Это также ограничение этой концепции, которое затрудняет понимание синхронизированного для некоторых новичков. Например, новички часто задаются вопросом:
- Почему любой объект может выступать в роли замка?
- В чем разница между блокировкой объекта Object и блокировкой класса?
- Какая блокировка используется синхронизированным модифицированным обычным методом?
- Какая блокировка используется синхронизированным модифицированным статическим методом?
Все это, собственно, ответ можно найти в Mark Word в структуре памяти объектов Java:
Многие ученики могут немного растеряться, впервые увидев эту картинку, не беда, я тоже очень большая, и она такая же.
Mark Word содержит много информации, но здесь нам нужно просто понимать его как метку, которая записывает информацию о блокировке. На приведенном выше рисунке показана память объектов Java на 32-разрядной виртуальной машине.Если вы внимательно посчитаете, то обнаружите, что все биты в сумме составляют ровно 32 бита. Структура под 64-битной виртуальной машиной аналогична, поэтому особо о ней вводить не будем.
Mark Word делит 2 бита из ограниченных 32 битов, которые специально используются в качестве битов флага блокировки.Говоря непрофессионалом, он отмечает текущий статус блокировки.
Просто потому, что у каждого объекта Java есть Mark Word, а Mark Word может отмечать состояние блокировки (думать о себе как о блокировке), любой объект в Java можно использовать как синхронизированную блокировку:
synchronized(person){
}
synchronized(student){
}
Так называемая эта блокировка — это текущий объект, а блокировка класса — это объект класса класса, к которому принадлежит текущий объект, который по сути является объектом Java. Нижний уровень синхронизированного модифицированного обычного метода использует текущий объект в качестве блокировки, а нижний уровень синхронизированного модифицированного статического метода использует объект класса в качестве блокировки.
Но если вы хотите гарантировать взаимное исключение нескольких потоков, самым основным условием является то, что ониИспользуйте тот же замок:
Добавлять две разные блокировки к одним и тем же данным бессмысленно, в реальной разработке следует избегать следующего написания:
synchronized(Person.class){
// 操作count
}
synchronized(person){
// 操作count
}
или
public synchronized void method1(){
// 操作count
}
public static synchronized void method1(){
// 操作count
}
синхронизированная и блокировка эскалации
После общего ознакомления со структурой памяти объектов Java давайте решим новый вопрос:
Зачем нужно отмечать состояние замка? Означает ли это, что синхронизированные блокировки имеют несколько состояний?
В более ранних версиях JDK реализация ключевого слова synchronized напрямую основывалась на тяжеловесных блокировках. Пока мы используем в коде synchronized, JVM будет обращаться к операционной системе за ресурсами блокировки (независимо от того, многопоточная это среда или нет), а обращение за блокировками к операционной системе является более ресурсоемким, который включает в себя пользовательский режим и режим ядра.Короче говоря, это более хлопотно и производительность не высока.
Чтобы решить проблему низкой производительности блокировки JVM, JDK представила ReentrantLock, основанную на CAS+AQS и аналогичную спин-блокировке. Спин означает, что в случае конкуренции замков нить, не выигравшая замок, примет метод вращения за дверью, чтобы дождаться освобождения замка, и тот, кто его схватит, выполнит его.
Преимущество спин-блокировок в том, что нет необходимости переключаться в состояние ядра для применения тяжеловесных блокировок операционной системы, а спин-ожидание может быть реализовано на уровне JVM. Однако в мире не существует панацеи, одновременно и полезной, и безвредной.Хотя спин CAS позволяет избежать сложных операций, таких как переключение состояний, он потребляет некоторые ресурсы ЦП, особенно когда ожидаемое время блокировки велико и параллелизм высок. вызывают одновременное вращение сотен или тысяч потоков, что значительно увеличивает нагрузку на ЦП.
В конце концов, синхронизированный является сыном JDK, поэтому, вероятно, в JDK1.6 или более ранних версиях официальные лица оптимизировали синхронизированный и предложили концепцию «эскалации блокировки», которая разделяла синхронизированную блокировку на несколько состояний, что упоминается на рисунке выше. .к:
- нет замка
- Блокировка смещения
- Облегченные блокировки (спинлоки)
- тяжелый замок
Lock-free — это состояние объекта Java, который только что был новым. Когда поток получает доступ к этому объекту в первый раз, поток «вставляет» свой идентификатор потока в свой заголовок (часть цифр в Mark Word была изменена), указывая «вы мой»:
В этот момент нет конфликтов за блокировку, поэтому нет блокировки или ожидания.
Зачем проектировать состояние «пристрастной блокировки»?
Вспомним, неужели в проекте так много одновременных сцен? Не совсем. Большую часть времени в большинстве проектов переменная выполняется одним потоком, и в настоящее время явно не нужно применять тяжеловесную блокировку непосредственно к операционной системе, потому что проблемы безопасности потоков вообще не будет.
Как только происходит конкуренция замков, синхронизированный будет обновлен до облегченного замка при определенных условиях, которые можно понимать как блокировку вращения, сколько раз вращать и когда отказываться от вращения, JDK также имеет набор связанных механизмов управления, вы можете понять для себя.
То же самое вращается, так что synchronized также столкнется с проблемой ReentrantLock: что, если время блокировки велико и много вращающихся потоков?
На этом этапе он будет снова обновлен и станет тяжеловесной блокировкой в традиционном смысле.По сути, операционная система будет поддерживать очередь, обменивать пространство на время, избегать одновременного вращения нескольких потоков и ждать, чтобы потреблять производительность ЦП, и просыпаться, когда заканчивается предыдущий поток. Ожидающий поток может участвовать в новом раунде конкуренции за блокировку.
Дальнейшее чтение (не обязательно):
Реализация нижнего уровня Deadly Synchronized — предвзятая блокировка
синхронизированный случай
Давайте вместе рассмотрим несколько случаев, чтобы углубить наше понимание синхронизированного.
- Являются ли синхронизированный метод m1 и метод m2 в одном классе взаимоисключающими?
Когда поток t1 выполняет метод m1, ему необходимо прочитать блокировку этого объекта, но потоку t2 не нужно читать блокировку.
- Можно ли вызвать синхронизированный метод m2 из синхронизированного метода m1 в том же классе?
Synchronized — это реентерабельная блокировка, которую можно приблизительно понять, так как тот же поток может снова получить блокировку, когда он уже удерживает блокировку, и будет выполнять операцию +1 для определенного количества состояний (ReentrantLock также поддерживает реентерабельность).
- Может ли синхронизированный метод m подкласса вызывать синхронизированный метод m родительского класса?
Прежде чем объект подкласса будет инициализирован, будет вызван конструктор родительского класса, что эквивалентно заключению объекта родительского класса в структуру с использованием объекта блокировки this.
- Являются ли статические синхронизированные методы и нестатические синхронизированные методы взаимоисключающими?
Каждый играет свой, не одинаковый замок, не говоря уже о взаимоисключениях.
Концепция распределенной блокировки Redis
Когда речь заходит о распределенных блокировках Redis, всегда возникают те или иные вопросы:
- что распространяется
- Что такое распределенная блокировка
- Зачем нужны распределенные блокировки
- Как Redis реализует распределенные блокировки
На первые три вопроса на самом деле можно ответить вместе, а как в Redis реализуются распределенные блокировки, мы расскажем в следующей статье.
Что распространяется? Это очень сложная концепция, и мне трудно выразить ее точно, поэтому давайте просто нарисуем картинку и посмотрим на нее:
Очень важной особенностью распределенного является то, что службы A и службы B, скорее всего, не развернуты на одном сервере, поэтому они не используют один и тот же участок памяти JVM. Как упоминалось выше, для достижения взаимного исключения потоков необходимо обеспечить, чтобы все обращающиеся к ним потоки использовали одну и ту же блокировку (блокировки JVM не могут гарантировать взаимное исключение в настоящее время).
Для распределенного проекта существует столько частей JVM-памяти, сколько серверов, и даже если в каждой части памяти установлена «уникальная» блокировка, блокировки в проекте в целом не уникальны.
На данный момент, как гарантировать, что потоки на каждой JVM совместно используют блокировку?
Ответ таков: извлеките блокировку и дайте потокам встретиться в одном и том же фрагменте памяти.
Однако блокировки не могут существовать из воздуха, суть по-прежнему в памяти, а в настоящее время кэш Redis может использоваться как хост-среда для замков, поэтому Redis может создавать распределенные блокировки.
Как выглядит блокировка Redis?
Синхронизированное ключевое слово и ReentrantLock являются реальными блокировками, которые были реализованы, и есть флаги. Но Redis — это всего лишь память… как его можно использовать как замок?
Одна вещь, которую все должны четко понимать, заключается в том, что причина, по которой Redis можно использовать для распределенных блокировок, заключается не только в том, что это часть памяти, в противном случае сама JVM также занимает память, так почему же она не может реализовать распределенные блокировки сама по себе?
Насколько я понимаю, для настройки распределенной блокировки необходимо выполнение как минимум нескольких условий:
- Видимый многопроцессный (часть памяти, независимая от многоузловой системы)
- Взаимное исключение (либо через один поток, либо через механизм выбора)
- возвращающийся
Redis может удовлетворить три вышеуказанных пункта. При трех вышеуказанных условиях то, как проектировать замки, полностью зависит от того, как люди определяют замки. Как и в реальной жизни, мы обычно понимаем замок как небольшой металлический предмет, который имеет замочную скважину и который нужно вставить в ключ. Однако существует более одной формы замков.С развитием технологий бесконечным потоком появляются замки отпечатков пальцев и замки радужной оболочки глаза, но в конечном счете причина, по которой они называются «замками», заключается в том, что они гарантируют «взаимное исключение». (Я да, ты нет).
Если мы сможем разработать логику, которая может вызвать «взаимоисключающее событие» в определенном сценарии, то ее можно будет назвать «замком». Например, известный интернет-магазин знаменитостей принимает только одного покупателя в день. У дверей не было продавца, поэтому внутри стоял автомат для снятия номера с билетом. Если вы опоздаете, билет пропадет, и вы не сможете войти в магазин. В этом сценарии клиенты без билетов не могут войти и заблокированы. В этот момент билетный автомат создал «событие взаимного исключения», поэтому его можно назвать «замком».
Redis предоставляет инструкцию setnx. Если ключ в настоящее время не существует, настройка выполнена успешно и возвращается значение true. В противном случае настройка не повторяется и возвращается значение false. Разве это не машина для подсчета чисел в мире программирования? Конечно, на самом деле используемая команда отличается от этой.Подробности читайте в следующей статье~
В этой статье рассказывается о распределенных блокировках Redis из блокировок JVM, а также рассказывается о структуре объектной памяти Java и основном принципе синхронизации.Я полагаю, что у каждого есть свое собственное понимание «блокировок». В следующей статье мы представим сценарии использования распределенных блокировок Redis на примере распределенных задач синхронизации.
Я bravo1988, увидимся в следующий раз.
よろしく・つづく
Прошлые статьи:
Что делать, если компания запрещает запрос JOIN?