До Java 5.0, единственные механизмы, которые можно использовать для координации доступа к общим объектам, были синхронизированными и летучими. Мы знаем, что синхронизированное ключевое слово реализует встроенные блокировки, в то время как летучие ключевое слово обеспечивает видимость памяти для многопоточных. В большинстве случаев эти механизмы могут хорошо выполнять работу, но они не могут реализовать некоторые более продвинутые функции, такие как неспособность прервать поток, ожидающий приобретения блокировки, неспособность реализовать ограниченный по времени механизм блокировки, неспособность реализовать правила блокировки для неблокирующих структур и т. Д. И эти более гибкие механизмы блокировки обычно обеспечивают лучшую активность или производительность. Таким образом, в Java 5.0 был добавлен новый механизм. Класс повторного отдела реализует интерфейс блокировки и обеспечивает такую же мютекс и видимость памяти, что и синхронизированный. Его основной слой заключается в достижении многопоточной синхронизации через AQ. По сравнению со встроенными замками, Reentrantlock не только обеспечивает более богатый механизм блокировки, но и не уступает встроенным замкам в производительности (даже лучше, чем встроенные замки в предыдущих версиях). Согласившись о многих преимуществах Reentrantlock, давайте раскрым его исходный код и увидим его конкретную реализацию.
1. Введение в синхронизированные ключевые слова
Java предоставляет встроенные замки для поддержки многопоточной синхронизации. JVM идентифицирует синхронизированный кодовый блок в соответствии с синхронизированным ключевым словом. Когда поток входит в синхронизированный кодовый блок, он автоматически приобретет блокировку. При выходе из блока синхронизированного кода, блокировка будет автоматически выпущена. После того, как одна потока приобретает блокировку, другие потоки будут заблокированы. Каждый объект Java может использоваться в качестве блокировки, который реализует синхронизацию. Синхронизированное ключевое слово может использоваться для изменения методов объектов, статических методов и кодовых блоков. При изменении методов объектов и статических методов блокировка является объектом, где находится метод, и объект класса. При изменении блока кода необходимо предоставить дополнительные объекты в виде блокировки. Причина, по которой каждый объект Java может быть использован в качестве блокировки, заключается в том, что объект монитора (манипуляции) связан в заголовке объекта. Когда поток входит в блок синхронного кода, он автоматически удержит объект монитора, а когда он выходит, он автоматически отпустит объект монитора. Когда объект монитора будет удерживаться, другие потоки будут заблокированы. Конечно, эти операции синхронизации реализованы базовым уровнем JVM, но все еще существуют некоторые различия в базовой реализации метода модификации синхронизированных ключевых слов и блока кода. Синхронизированный метод модификации ключевых слов неявно синхронизирован, то есть его не нужно контролировать с помощью инструкций по байт -коду. JVM может различать, является ли метод синхронизированным методом, основанным на флаге ACC_SYNCHRONIZED доступа в таблице методов; В то время как код блокируется, измененные с помощью синхронизированного ключевого слова, явно синхронизируются, которые управляют удерживанием и высвобождением потока через инструкции по мониторингу и мониторексуальному байткоду. Объект монитора удерживает поле _count внутренне. _count, равный 0, означает, что трубопровод не удерживается, а _count больше 0 означает, что трубопровод был удерживается. Каждый раз, когда переосмысление потока удержания, _count будет добавляться 1, и каждый раз, когда выход сдержания выхода из потока, _count будет уменьшаться на 1. Это принцип реализации встроенного заповедника. Кроме того, внутри объекта монитора есть две очереди _Entrylist и _waitSet, которые соответствуют очереди синхронизации и условной очереди AQ. Когда поток не сможет приобрести блокировку, он будет заблокировать в _Entrylist. Когда будет вызван метод ожидания объекта блокировки, поток войдет в _waitset, чтобы подождать. Это принцип реализации синхронизации потоков и условного ожидания встроенных замков.
2. Сравнение между повторным делом и синхронизированным
Синхронизированное ключевое слово-это встроенный механизм блокировки, предоставляемый Java. Его операции синхронизации реализованы базовым JVM. Reentrantlock - это явный блокировка, предоставленная пакетом Java.Util.concurrent, а его операции синхронизации оснащены синхронизатором AQS. Reentrantlock предоставляет ту же семантику на блокировке и памяти, что и встроенные блокировки, кроме того, он предоставляет некоторые другие функции, включая ожидание блокировки, прерываемое ожидание блокировки, справедливую блокировку и реализацию структурированной блокировки. Кроме того, Reentrantlock также имел определенные преимущества производительности в ранних версиях JDK. Поскольку у Reentrantlock так много преимуществ, почему мы должны использовать синхронизированное ключевое слово? Фактически, многие люди используют Reentrantlock, чтобы заменить операцию блокировки синхронизированных ключевых слов. Тем не менее, встроенные замки все еще имеют свои преимущества. Встроенные замки знакомы многим разработчикам и являются более простыми и компактными в использовании. Поскольку явные блокировки должны быть вручную называют разблокировку в блоке, наконец, относительно безопаснее использовать встроенные замки. В то же время это, скорее всего, улучшит производительность синхронизированного, а не повторного блока в будущем. Поскольку синхронизированный является встроенным свойством JVM, он может выполнять некоторые оптимизации, такую как оптимизация устранения блокировки для объектов блокировки, устранения синхронизации встроенных замков за счет увеличения детализации блокировки, и если эти функции реализованы через классовые библиотечные блокировки, это не однозначно. Таким образом, когда необходимы некоторые передовые функции, следует использовать повторный зал, который включает в себя: временные, загрязненные и прерывимые операции с приобретением блокировки, справедливые очереди и блокировки неблокировки. В противном случае синхронизированный должен использоваться в первую очередь.
3. Операции приобретения и выпуска замков
Давайте сначала посмотрим на пример кода, используя повторный разлок, чтобы добавить блокировки.
public void dosomething () {// По умолчанию-получение безболочного блокировки Lock = new Reentrantlock (); try {// lock lock.lock () перед выполнением; // Выполнить операцию ...} Наконец {// lock lock.unlock () наконец -то выпускает; }}Ниже приведено API для получения и выпуска замков.
// Операция получения блокировки public void lock () {sync.lock ();} // Операция выпуска блокировки public void unlock () {sync.release (1);}Вы можете видеть, что операции получения блокировки и освобождения блокировки делегированы методом блокировки и методу выброса объекта Sync соответственно.
Общедоступный класс Reentrantlock реализует Lock, java.io.serializable {Private Final Sync Sync; Abstract Static Class Sync Extraves AbstractQueuedSynchronizer {Abstract void Lock (); } // Синхронизатор, который реализует не FAIR Lock Static Final Class NonfairSync Extends Sync {final void lock () {...}} // Синхронизатор, который реализует справедливую статическую статическую конечную класс FairSync Extends {final void Lock () {...}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}Каждый объект Reenterlock содержит ссылку на синхронизацию типа. Этот класс синхронизации является абстрактным внутренним классом. Это наследует от AbstractQueuedSynchronizer. Метод блокировки внутри его является абстрактным методом. Синхронизация членов reentrantlock. Давайте посмотрим, что делают два метода конструктора Reentrantlock?
// Конструктор без параметра по умолчанию public reentrantlock () {sync = new nonfairsync ();} // Параметризованный конструктор Public Reentrantlock (Boolean Fair) {sync = fair? new Fairsync (): new nonfairsync ();}Вызов конструктора без параметра по умолчанию назначит экземпляр NonfairSync для синхронизации, а блокировка не является блокировкой в настоящее время. Конструктор параметров позволяет параметрам указать, назначать ли синхронизацию экземпляра Fairsync или экземпляра без фарсинового экземпляра. Nonfairsync и Fairsync как наследуют от класса синхронизации, так и переписывают метод Lock (), поэтому существуют некоторые различия между честными замками и блокировками, не связанными с F-FAIR на пути получения замков, о которых мы поговорим ниже. Давайте посмотрим на операцию выпуска блокировки. Каждый раз, когда вы называете метод Unlock (), вы просто выполняете операцию Sync.Release (1). Эта операция будет вызвать метод RELESS () класса AbstractQueuedSynchronicrizer. Давайте рассмотрим это снова.
// Отпустите операцию блокировки (эксклюзивный режим) Public Final Boolean Release (int arg) {// Поверните блокировку пароля, чтобы увидеть, может ли он разблокировать if (tryRelease (arg)) {// Получить узлом узла головного узла H = Head; // Если узел голов не является пустым, а состояние ожидания не равно 0, разбудите узел преемника if (h! = Null && h.waitstatus! = 0) {// Пробуждение узла преемника UnparkSuccessor (h); } вернуть true; } вернуть false;}Этот метод выпуска является API для выпуска операций блокировки, предоставленных AQS. Сначала он вызывает метод TryRelease, чтобы попытаться приобрести блокировку. Метод TryRelease является абстрактным методом, а его логика реализации находится в синхронизации подкласса.
// Попробуйте выпустить защищенную блокировку Final Boolean TryRelease (int leleases) {int c = getState () - выпуски; // Если поток, удерживающий блокировку, не является текущим потоком, будет брошено исключение, если (thread.currentThread ()! = GetExclusiveOwnerThread ()) {Throw New allosalMonitorStateException (); } boolean free = false; // Если статус синхронизации равен 0, это означает, что блокировка выпускается, если (c == 0) {// Установите флаг блокировки, выпущенного как true free = true; // Установить занятый потоком в пустую SetExclusiveOwnerThread (null); } setState (c); вернуть бесплатно;}Этот метод TryRelease сначала приобретет текущее состояние синхронизации, вычитайте текущее состояние синхронизации из передаваемых параметров к новому состоянию синхронизации, а затем определит, равно ли новое состояние синхронизации равным 0. Если оно равно 0, это означает, что текущее блокировка выпускается. Затем установите состояние выпуска в True, затем очистите поток, который в настоящее время занимает блокировку, и, наконец, вызовите метод SetState, чтобы установить новое состояние синхронизации и вернуть состояние выпуска блокировки.
4. Справедливый замок и несправедливый замок
Мы знаем, какой конкретный экземпляр - это повторный зал, указывающий на основе синхронизации. Во время строительства будет назначена синхронизация переменной элемента. Если значение присваивается экземпляру NonfairSync, это означает, что это не FIAR Lock, и если значение назначается экземпляру FairSync, это означает, что это справедливая блокировка. Если это справедливая блокировка, потоки получат блокировку в том порядке, в котором они выполняют запросы, но при несправедливой блокировке разрешено поведение выреза: когда поток запрашивает несправедливую блокировку, если состояние блокировки становится доступным в то же время, что и запрос, поток пропустит все резьбы ожидания в основе, чтобы получить непосредственно. Давайте посмотрим, как получить несправедливые замки.
// Несоверный синхронизатор статический окончательный класс nonfairsync extends sync {// реализовать метод абстрактного родительского класса для получения блокировки Final void lock () {// Использовать метод CAS, чтобы установить состояние синхронизации, если (CompareAndSetState (0, 1)) {// Если настройка является успешной, это означает, что блокировка не занимает setExcliewuseuliveOwliewUlceUlceulaIn что -то. } else {// в противном случае это означает, что блокировка была занята, позвонила и пусть поток очереди синхронизировать очередь для получения (1); }} // метод, чтобы попытаться приобрести защищенную блокировку Final Boolean TryAcquire (int приобретает) {return nonfairtryacquire (приобретает); }} // Приобретение блокировки в режиме неинтеррота (Exclusive Mode) public final void accire (int arg) {if (! TryAcquire (arg) && accirequed (addWaiter (node.exclusive), arg)) {selfUnterrupt (); }}Можно видеть, что в методе блокировки несправедливой блокировки поток изменит значение состояния синхронизации с 0 до 1 на первом этапе в CAS. Фактически, эта операция эквивалентна попытке приобрести блокировку. Если изменение успешно, это означает, что поток приобрел блокировку только сейчас, и больше нет необходимости в очереди в очереди синхронизации. Если изменение не удается, это означает, что блокировка не была выпущена, когда поток сначала появляется, поэтому метод приобретения называется следующим. Мы знаем, что этот метод приобретения унаследован от метода AbstractQueuedSynchronizer. Давайте рассмотрим этот метод. После того, как поток вступит в метод приобретения, первый вызов метода TryAcquire, чтобы попытаться получить блокировку. Поскольку Nonfairsync перезаписывает метод TryAcquire и вызывает метод нефтратрии синхронизации родительского класса в методе, здесь будет вызван метод NonfairtryAcquire, чтобы попытаться приобрести блокировку. Посмотрим, что делает этот метод конкретно.
// несправедливое приобретение блокировки окончательного логического новинка nonfairtryacquire (int quires) {// Получить текущий поток окончательного потока ток = think.currentThread (); // Получить текущее состояние синхронизации int c = getState (); // Если состояние синхронизации равно 0, это означает, что блокировка не занята, если (c == 0) {// Использовать CAS для обновления состояния синхронизации if (CompareAndsetState (0, приобретает)) {// Установить поток, в настоящее время занимающий блокировку SetExcluseWuseWardThread (current); вернуть истину; } // В противном случае определяется, является ли блокировка текущим потоком} else if (current == getExclusiousOwnThread ()) {// Если блокировка удерживается текущим потоком, непосредственно измените текущее состояние синхронизации intc = c + приобретает; if (nextc <0) {бросить новую ошибку ("максимальный счет блокировки превышен"); } setState (nextc); вернуть истину; } // Если блокировка не является текущим потоком, верните флаг сбоя вернуть false;}Метод Nonfairtryacquire - это метод синхронизации. Мы видим, что после того, как поток вступит в этот метод, он сначала получает состояние синхронизации. Если состояние синхронизации равно 0, используйте операцию CAS, чтобы изменить состояние синхронизации. На самом деле, это снова приобрести замок. Если состояние синхронизации не 0, это означает, что замок занят. На данный момент мы сначала определим, является ли потока, удерживающей блокировку текущим потоком. Если это так, то состояние синхронизации будет увеличено на 1. В противном случае операция попытки приобрести блокировки не удастся. Таким образом, метод AddWaiter будет вызван для добавления потока в очередь синхронизации. Подводя итог, в режиме несправедливой блокировки поток попытается приобрести два замка перед входом в очередь синхронизации. Если приобретение будет успешным, оно не будет входить в очередь очередей очередей синхронизации очередей, в противном случае оно войдет в очередь очередей очередей синхронизации очередей. Затем давайте посмотрим, как получить честные замки.
// Синхронизатор, который реализует справедливую блокировку статического окончательного класса FairSync Extends Sync {// Реализуйте метод абстрактного родительского класса для приобретения блокировки окончательного void lock () {// Приобретение приобретения и позвольте потоке, чтобы синхронизировать очередь, чтобы получить приобретение (1); } // Попробуйте приобрести блокировку, защищенную окончательным логическим пулевым tryAcquire (int приобретает) {// Получить текущий поток окончательный поток ток = think.currentThread (); // Получить текущее состояние синхронизации int c = getState (); // Если состояние синхронизации 0 означает, что блокировка не занята if (c == 0) {// Защита, есть ли прямой узел в очереди синхронизации if (! Hasqueudpredecessors () && comparaneAndsetState (0, приобретает)) {// Если нет прямого узла, а состояние синхронизации успешно означает, что заблокируемый забло SetExclusiveOwnterThread (Current); вернуть истину; } // В противном случае определите, сохраняет ли текущий поток блокировку} else if (current == getExclusiveharthread ()) {// Если текущий поток удерживает блокировку, непосредственно измените состояние синхронизации intc = c + eakers; if (nextc <0) {бросить новую ошибку ("максимальный счет блокировки превышен"); } setState (nextc); вернуть истину; } // Если текущий поток не удерживает блокировку, сбоя сбоя возврата false; }} При вызове метода блокировки справедливой блокировки метод приобретения будет вызван непосредственно. Аналогичным образом, метод приобретения сначала вызывает метод переписывания Fairsync TryAcquire, чтобы попытаться получить замок. В этом методе сначала получено значение состояния синхронизации. Если состояние синхронизации равно 0, это означает, что замок выпускается в настоящее время. Разница от несправедливой блокировки заключается в том, что он сначала вызовет метод Hasqueudpredecessors, чтобы проверить, находится ли кто -то в очереди в очереди синхронизации. Если никто не проходит очередь, значение состояния синхронизации будет изменено. Вы можете видеть, что справедливая блокировка принимает здесь метод вежливости вместо того, чтобы немедленно приобретать замок. За исключением этого шага, который отличается от несправедливой блокировки, другие операции одинаковы. Подводя итог, мы видим, что справедливая блокировка проверяет статус блокировки только один раз перед тем, как вступить в очередь синхронизации. Даже если вы обнаружите, что замок открыт, вы не получите его немедленно. Вместо этого вы позволите потокам в очереди синхронизации сначала приобрести его. Следовательно, можно гарантировать, что порядок, в котором все потоки приобретают замки под справедливым замком, был сначала, а затем прибыл, что также обеспечивает справедливость получения замков.
Так почему же мы не хотим, чтобы все замки были справедливыми? В конце концов, справедливость - это хорошее поведение, а несправедливость - плохое поведение. Поскольку операции приостановки и пробуждения потока имеют большие накладные расходы, она влияет на производительность системы, особенно в случае жесткой конкуренции, честные замки приведут к частым операциям приостановки и пробуждения потоков, в то время как блокировки, не являющиеся FIAR, могут уменьшить такие операции, поэтому они будут лучше, чем справедливые замки в производительности. Кроме того, поскольку большинство потоков используют блокировки в течение очень короткого времени, а операция пробуждения потока будет иметь задержку, возможно, что потока B немедленно приобретет блокировку и отпустит блокировку после его использования. Это приводит к беспроигрышной ситуации. В тот момент, когда резьба приобретает блокировку, не задерживается, но резьба B использует блокировку заранее, и его пропускная способность также была улучшена.
5. Механизм реализации условных очередей
Есть некоторые дефекты в встроенной очереди условий. Каждый встроенный блокировка может иметь только одну связанную очередь состояния, которая заставляет несколько потоков ждать предикатов различных условий в одной и той же очереди состояния. Затем, каждый раз, когда называется уведомление, все потоки ожидания будут пробуждены. Когда нить просыпается, она обнаруживает, что это не тот условие, которого он ждет, и он будет приостановлен. Это приводит к многим бесполезным пробуждению потока и приостановке операций, которые будут тратить много системных ресурсов и снизить производительность системы. Если вы хотите написать одновременный объект с несколькими условными предикатами, или если вы хотите получить больше контроля, чем видимость условной очереди, вам необходимо использовать явную блокировку и состояние вместо встроенных замков и условных очередей. Состояние и замки связаны вместе, как очередь состояния и встроенный замок. Чтобы создать условие, вы можете вызвать метод блокировки. Давайте сначала посмотрим на пример с использованием условия.
public class boundedbuffer {final lock lock = new Reentrantlock (); окончательное условие natfull = lock.newcondition (); // Состояние Предикат: Замеченное окончательное условие notempty = lock.newcondition (); // Состояние Предикат: Notempty Final Object [] items = new Object [100]; int putptr, takeptr, count; // метод производства public void put (объект x) бросает прерывание {lock.lock (); попробуйте {while (count == item.length) natufull.await (); // очередь заполнена, и поток ждет предметов [Putptr] в заметной очереди. элементы [putptr] = x; if (++ putptr == item.length) putptr = 0; ++ подсчет; notempty.signal (); // Производство успешно, разбудите узел очереди Нотмпти}, наконец, {lock.unlock (); }} // Потребление метода public object () Throws treamptenException {lock.lock (); попробуйте {while (count == 0) notempty.await (); // Очередь пуста, потоковой щиток ждет объекта x = элементы [TakePtr] в Queue Notempty; if (++ tableptr == item.length) tableptr = 0; --считать; natfull.signal (); // Потребление успешно, разбудите узел заметного возврата очереди x; } наконец {lock.unlock (); }}}Объект блокировки может генерировать несколько очередей состояния, и здесь генерируются две очереди состояния. Когда контейнер заполнен, поток, который вызывает метод POT, необходимо блокировать. Подождите, пока предикат условия не станет истинной (контейнер не удовлетворен) просыпается и продолжает выполнять; Когда контейнер пуст, поток, который вызывает метод принятия метода, необходимо блокировать. Подождите, пока предикат условия не станет истинной (контейнер не пуст) просыпается и продолжает выполнять. Эти два типа потоков ждут в соответствии с различными предикатами условий, поэтому они введут две разные очереди состояния, чтобы заблокировать, и подождать до подходящего времени, прежде чем проснуться, позвонив в API на объекте Condition. Ниже приведен код реализации метода NewCondition.
// Создать очередь условия публичного условия newCondition () {return sync.newcondition ();} абстрактный статический класс Sync Extends AbstractQueuedSynchronizer {// Создать новый объект условия Final ConditionObject newCondition () {return newdentObject (); }}Реализация очереди условий на reentrantlock основана на AbstractQueuedSynchronizer. Объект условий, который мы получаем при вызове метода Нью -Кондиционирования, является экземпляром внутреннего условия класса AQS. Все операции в очереди в условиях выполняются путем вызова API, предоставленного ConditionObject. Для конкретной реализации ConditionObject вы можете проверить мою статью «Серия параллелизма Java [4] ------ AbstractQueuedSynchronizer Анализ исходного кода условной очередь», и я не буду повторять его здесь. На этом этапе наш анализ исходного кода Reentrantlock подошел к концу. Я надеюсь, что чтение этой статьи поможет читателям понять и освоить повторный зал.
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.