Использует ли синхронизированная конструкция в Java внутренне (и каким-то образом) аппаратную примитивную операцию CAS? - PullRequest
0 голосов
/ 20 ноября 2018

Мне трудно понять, что такое аппаратная поддержка для оператора synchronized и связанных с ним методов notify(), notifyAll() и wait(), присутствующих в каждом объекте java.

Я читал и знаю, как использовать эти конструкции, но я всегда предполагал, что они были сопоставлены непосредственно с аппаратными примитивами.

Поскольку я углубляюсь в книги о параллелизме, я читаю только о сравнении.Операция and-swap (CAS), предоставляемая напрямую аппаратным обеспечением.

Кажется, что эти конструкции создаются / поддерживаются самой JVM.Если мое чтение верно, каждый объект содержит некоторое состояние с информацией о потоке, обращающемся к нему.Это используется для определения монитора этого объекта и координации доступа нескольких потоков к этому объекту.
Но если это так, то как само это состояние управляется из одновременного доступа ?Надо обязательно управлять, правильно?Это с CAS?

Если это с CAS, это означает, что существует только одна реальная форма синхронизации, CAS.Все остальные являются производными.Почему тогда была построена эта конструкция монитора с соответствующими методами synchronized, notify(), notifyAll(), wait(), учитывая, что переменные Atomic (т.е. CAS) лучше с точки зрения производительности и также ожидают-свободно ?

Мне известно, что Атомные переменные для пользовательских классов появились только после Java 5.0 или около того, но до этого Java уже контролировала / встроенные блокировки.Как они были реализованы?

1 Ответ

0 голосов
/ 21 ноября 2018

Урегулируйте у детей, это будет долго.

Первое, что нужно обсудить CAS ( C ompare A nd S wap) это не механизм синхронизации.Это атомарная операция, которая позволяет нам обновлять значение в основной памяти, проверяя одновременность, если это значение не изменилось (или мы ожидаем, что оно будет).Здесь нет блокировки.Хотя они используются некоторыми примитивами синхронизации (семафоры, мьютексы).Давайте посмотрим на следующий пример:

              a = 1;
--------------------------------
     Thread 1   |  Thread 2 
    b = 1 + a   |   b = 2 + a
 cas(*a, 1, b ) | cas(*a, 1, b )

Теперь одна из CAS-сбоев выйдет из строя, и я имею в виду, что она вернет false.Другой вернет true, и значение, которое представляет указатель * a, будет обновлено новым значением.Если бы мы не использовали CAS, а вместо этого просто обновили значение, например, так:

              a = 1;
--------------------------------
     Thread 1   |  Thread 2 
    b = 1 + a   |   b = 2 + a
      a = b     |     a = b

В конце этого вычисления значение a может быть 2 или 3, и оба потока завершатся счастливо, не зная, какое значение былосохранено в.Это то, что называется гонкой данных, и CAS - способ решить эту проблему.

Существование CAS позволяет нам писать некоторые алгоритмы без блокировки (синхронизация не требуется), например, коллекции в java.util.concurrent.пакет, который не нужно синхронизировать, для одновременного доступа.

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

synchronized(this){ 
     n = n + 1; 
}

AtomicLong al = new AtomicLong();
al.updateAndGet( n -> n + 1)

Снижение производительности, которое вы можете получить при использовании CAS против синхронизации, происходит из-за сбоя CAS, вы можете просто повторить попытку, в то время как использование синхронизации может привести к тому, что поток перейдет в спящий режим.Переход к кроличьей норе переключателей контекста (это может или не может произойти :) в зависимости от ОС).

Теперь для notify(), notifyAll() and wait().Вызывает напрямую в планировщик потоков, который является частью ОС.Планировщик имеет две очереди Очередь ожидания и Очередь выполнения .Когда вы вызываете ожидание потока, этот поток помещается в wq и сидит там, пока не получит уведомление и не поместит его в rq, чтобы он был выполнен как можно скорее.

В Java существует две основные синхронизации потоков, одна из которых (wait (), notify ()) называется кооперация , а другая - через блокировки, называемые взаимное исключение (мьютекс),И это, как правило, для параллельных треков, чтобы делать мысли сразу.

Теперь я не знаю, как была выполнена синхронизация до Java 5. Но теперь у вас есть 2 способа синхронизации с использованием объекта (один из возможныхстарый другой новый).

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

  2. (это, вероятно, старый) с использованием monitorenter/monitorexit.Это инструкции байт-кода.Они размещаются на входе и выходе оператора synchronize {...}.Вот где идентичность объекта становится актуальной.Как это становится частью информации о блокировке.

ОК, что это.Я знаю, что не ответил на вопрос полностью.Тема такая сложная и такая сложная.Глава 17 «Спецификации языка Java» под названием: «Модель памяти Java», вероятно, единственная, которую не могут прочитать обычные программисты (возможно, динамическая диспетчеризация также подпадает под эту категорию :)).Я надеюсь, что по крайней мере вы сможете гуглить правильные слова.

Пара ссылок: https://www.artima.com/insidejvm/ed2/threadsynchP.html (monitorenter / monitorexit, пояснение)

https://www.ibm.com/developerworks/library/j-jtp10185/index.html (как оптимизируются блокировки внутри jvm)

...