Во -первых, давайте представим несколько оптимистичных замков и пессимистических замков:
Пессимистический замок: всегда принимайте худший случай. Каждый раз, когда я иду, чтобы получить данные, я думаю, что другие изменяют их, поэтому я буду блокировать их каждый раз, когда получаю данные, чтобы другие заблокировали их, пока он не получит блокировку. Традиционные реляционные базы данных используют много таких механизмов блокировки, таких как блокировки строк, блокировки таблицы и т. Д., Читать блокировки, блокировки записи и т. Д., Которые заблокируются перед выполнением операций. Например, реализация синхронизированного ключевого слова ключевого слова в Java также является пессимистической блокировкой.
Оптимистичная блокировка: как следует из названия, это означает очень оптимистичный. Каждый раз, когда я иду, чтобы получить данные, я думаю, что другие не будут их изменять, поэтому я не буду блокировать их. Однако при обновлении я буду судить, обновили ли другие данные в течение этого периода, и могут использовать номер версии и другие механизмы. Оптимистичные блокировки подходят для множественных типов приложений, которые могут улучшить пропускную способность. Например, база данных обеспечивает оптимистичную блокировку, аналогичную механизму write_condition, но на самом деле все они предоставляются оптимистичными замками. В Java класс атомной переменной в рамках пакета java.util.concurrent.atomic реализуется CAS с использованием оптимистичного блокировки.
Реализация оптимистичного блокировки (сравните и обмен):
Проблемы с блокировкой:
До JDK1.5 Java полагалась на синхронизированные ключевые слова, чтобы обеспечить синхронизацию. Таким образом, используя постоянный протокол блокировки для координации доступа к общему состоянию, он может гарантировать, что независимо от того, какой поток содержит блокировку общих переменных, он использует эксклюзивный метод для доступа к этим переменным. Это своего рода эксклюзивный замок. Эксклюзивная блокировка на самом деле является своего рода пессимистическим замком, поэтому можно сказать, что синхронизированный - пессимистический замок.
Пессимистический механизм блокировки имеет следующие проблемы:
1. При соревнованиях по многопользованию, добавление и выпуск замков приведет к увеличению переключения контекста и задержек планирования, что приведет к проблемам с производительностью.
2. Поток, удерживающая блокировку, вызовет все другие потоки, которые требуют, чтобы этот замок вешал.
3. Если поток с высоким приоритетом ждет потока с низким приоритетом, чтобы выпустить блокировку, это вызовет приоритетную инверсию, вызывая риски производительности.
По сравнению с этими проблемами пессимистических замков, еще одна более эффективная блокировка является оптимистичным замками. На самом деле, оптимистичная блокировка: каждый раз, когда вы не добавляете блокировку, но вы выполняете операцию, предполагая, что нет одновременного конфликта. Если одновременный конфликт не удается, попробуйте еще раз, пока он не преуспеет.
Оптимистичный замок:
Оптимистическая блокировка была упомянута выше, но на самом деле это своего рода мысль. По сравнению с пессимистическими замками, оптимистичные блокировки предполагают, что данные обычно не вызывают одновременных конфликтов, поэтому, когда данные отправляются и обновляются, они официально обнаруживают, имеют ли данные одновременные конфликты. Если найден одновременный конфликт, неправильная информация пользователя будет возвращена, и пользователь решает, как это сделать.
Концепция оптимистичной блокировки, упомянутая выше, фактически объяснила ее конкретные детали реализации: в основном она включает в себя два шага: обнаружение конфликтов и обновление данных. Одним из типичных методов реализации является сравнение и обмен (CAS).
CAS:
CAS - это оптимистичная технология блокировки. Когда несколько потоков пытаются использовать CAS для обновления одной и той же переменной одновременно, только один из потоков может обновить значение переменной, в то время как другие потоки сбой. Неудачный поток не будет приостановлен, но будет сказано, что этот соревнование не удалось и может попробовать еще раз.
Операция CAS содержит три операнда - местоположение памяти (v), которое необходимо прочитать и записан, ожидаемое исходное значение (a) для сравнения и новое значение (b). Если значение позиции памяти V соответствует ожидаемому исходному значению A, процессор автоматически обновит значение позиции до нового значения B. В противном случае процессор ничего не сделает. В любом случае он возвращает значение этого местоположения перед директивой CAS. (В некоторых особых случаях CAS, только независимо от того, является ли CAS успешным или нет, без извлечения текущего значения.) CAS эффективно утверждает, что «я думаю, что позиция V должна содержать значение a; если оно содержится, положите B в эту позицию; в противном случае не меняйте позицию, просто сообщайте мне текущее значение этой позиции». На самом деле это то же самое, что и принцип проверки конфликтов + обновление данных оптимистичных замков.
Позвольте мне подчеркнуть, что оптимистичная блокировка - это своего рода мысль. CAS - это способ осознать эту идею.
Поддержка Java для CAS:
Новая java.util.concurrent (JUC) в JDK1.5 построена на CAS. По сравнению с алгоритмами блокировки, такими как синхронизированный, CAS является распространенной реализацией неблокирующих алгоритмов. Поэтому JUC значительно улучшил свою производительность.
Возьмите Atomicinteger в java.util.concurrent в качестве примера, чтобы увидеть, как обеспечить безопасность потока без использования замков. В основном мы понимаем метод GetAndIncrement, который эквивалентен операции ++ I.
Public Class AtomicInteger Extends Number реализует Java.io.serializable {Private Elatile Int Value; public final int get () {return value; } public final int getAndIncrement () {for (;;) {int current = get (); int next = current + 1; if (CompareAndset (current, Next)) возврат ток; }} public final Boolean CompareandEset (int weard, int update) {return uncafe.compareandswapint (это, valueoffset, weals, update); }}В механизме без замков значение поля должно использоваться для обеспечения видимости данных между потоками. Таким образом, вы можете читать непосредственно, когда получаете значение переменной. Тогда давайте посмотрим, как ++ я закончил.
GetAndIncrement использует работу CAS, и каждый раз, когда вы читаете данные из памяти, затем выполняете работу CAS на этих данных и результат после +1. В случае успеха, результат будет возвращен, в противном случае повторите попытку, пока не успешно.
CompareandSet использует JNI (нативный интерфейс Java) для завершения операции инструкций процессора:
public final Boolean Compareandeset (int ожидайте, int update) {return uncafe.compareAndswapint (это, valueOffset, weal, обновление); }где небезопасно. похож на следующую логику:
if (this == ожидаете) {this = обновление вернуть true; } else {return false; }Итак, как сравнить это == Ожидайте, замените это = обновление, Comparandswapint для достижения атомальности этих двух шагов? Обратитесь к принципам CAS
Принцип CAS:
CAS реализован путем вызова кода JNI. CompareAndswapint реализуется с помощью C для вызова основных инструкций процессора.
Следующее объясняет принцип реализации CAS из анализа более часто используемого ЦП (Intel X86).
Вот исходный код метода CompareAndswapint () класса sun.misc.unsafe:
Общественный финальный логический логический баланс (объект O, Long Offset, int ожидается, int x);
Вы можете видеть, что это локальный вызов метода. Код C ++, который вызывает этот локальный метод в JDK:
#define lock_if_mp (mp) __asm cmmp mp, 0 / __asm je l0 / __asm _emit 0xf0 / __asm l0: inline jint atomic :: cmpxchg (Jint Exchange_value, volatile Jint* dest, Jint compare_value) { / / alferaity for for for in in int mp in in in in in in in in in in in in in inp in in in in in in jint* jint compare_value). os :: is_mp (); __asm {mov edx, dest mov ecx, Exchange_value move eax, compare_value lock_if_mp (mp) cmmpxchg dword ptr [edx], ecx}}Как показано в приведенном выше исходном коде, программа будет решать, добавить ли префикс блокировки к инструкции CMMPXCHG на основе типа текущего процессора. Если программа работает на многопроцессоре, добавьте префикс блокировки в инструкцию CMMPXCHG. Напротив, если программа работает на одном процессоре, префикс блокировки пропущен (сам отдельный процессор поддерживает последовательную согласованность в одном процессоре и не требует эффекта барьера памяти, обеспечиваемого префиксом блокировки).
CAS недостатки:
1. Вопросы ABA:
Например, если поток, который вынимает из положения V от памяти V, то другой поток также вынимает A From Memory, а два выполняют некоторые операции и становится B, а затем два поворачивают данные в положении V. В настоящее время поток один выполняет работу CAS и обнаруживает, что A все еще находится в памяти, а затем один работает успешно. Несмотря на то, что операция по сравнению с CAS успешна, могут быть скрытые проблемы. Как показано ниже:
Существует стек, реализованный с односторонним связанным списком, причем верхняя часть стека-A. В настоящее время поток T1 уже знает, что A.Next IS B, а затем надеется заменить верхнюю часть стека B на CAS:
Head.comPareAndset (a, b);
Перед тем, как T1 выполнит вышеуказанную инструкцию, Thread T2 вмешивается, вытаскивает A и B из стека, а затем Pushd, C и A. В настоящее время структура стека выглядит следующим образом, а объект B находится в свободном состоянии в настоящее время:
В настоящее время настала поворот потока T1 для выполнения операции CAS. Обнаружение обнаружило, что верхняя часть стека все еще является A, поэтому CAS добивается успеха, и вершина стека становится B, но на самом деле B.Next - это нулевая, поэтому ситуация в настоящее время становится:
В стеке есть только один элемент B, и в стеке больше нет связанного списка, состоящего из C и D. C и D выброшены без причины.
Начиная с Java 1.5, атомный пакет JDK обеспечивает класс AtomicSmappedReference для решения проблемы ABA. Метод сравнения этого класса должен сначала проверить, равна ли текущая ссылка ожидаемой ссылке и будет ли текущий флаг равен ожидаемому флагу. Если все равны, ссылка и значение флага устанавливаются на данное обновленное значение атомно.
Общественный логический сравнение (v wederreference, // ожидаемая ссылка v newreference, // Обновленная ссылка int wedstamp, // weding flag int newStamp // Обновленный флаг)
Фактический код приложения:
Частный статический AtomicStampedReference <Integer> atomicStampedRef = new AtomicStampedReference <Integer> (100, 0); ......... AtomicStampedRef.compareandDset (100, 101, марка, марок + 1);
2. Длинное время цикла и высокие накладные расходы:
Spin CAS (если он не удастся, он будет выполняться ездить на велосипеде до тех пор, пока он не преуспеет), если он не удастся в течение длительного времени, он принесет большие накладные расходы на процессор. Если JVM может поддержать инструкции по паузе, предоставленные процессором, эффективность будет улучшена в определенной степени. Инструкции по паузе имеют две функции. Во-первых, он может задержать инструкции по выполнению конвейера (depipeline), чтобы ЦП не использовал слишком много ресурсов выполнения. Время задержки зависит от конкретной версии реализации. На некоторых процессорах время задержки равна нулю. Во -вторых, он может избежать промывки трубопровода процессора, вызванного нарушением заказа памяти при выходе из цикла, тем самым повышая эффективность выполнения процессора.
3. Можно гарантировать только атомные операции общей переменной:
При выполнении операций по общей переменной мы можем использовать метод циклического CAS для обеспечения атомных операций. Однако при эксплуатации нескольких общих переменных циклический CAS не может гарантировать атомность операции. В настоящее время вы можете использовать замки или есть трюк, который состоит в том, чтобы объединить несколько общих переменных в общую переменную для работы. Например, есть две общие переменные i = 2, j = a, merge ij = 2a, а затем используйте CAS для эксплуатации IJ. Начиная с Java 1.5, JDK предоставляет класс AtomicReference, чтобы обеспечить атомичность между ссылочными объектами. Вы можете поместить несколько переменных в один объект для работы CAS.
CAS и синхронизированные сценарии использования:
1. В ситуациях, когда существует меньшая конкуренция за ресурсами (конфликт с легким потоком), используя блокировку синхронизированной синхронизации для блокировки потоков и операций переключения пробуждения и переключения между состояниями ядра пользователя, является дополнительной тратой ресурсов ЦП; Хотя CAS реализуется на основе аппаратного обеспечения, не нужно входить в ядро, не нужно переключать потоки, а вероятность работы спинов меньше, поэтому можно получить более высокую производительность.
2. Для ситуаций, когда конкуренция за ресурсами является серьезной (тяжелый конфликт потока), вероятность спина CAS относительно высока, которая тратит больше ресурсов ЦП и менее эффективна, чем синхронизирована.
Дополнение: синхронизированный был улучшен и оптимизирован после JDK1.6. Основная реализация синхронизированной в основном зависит от очереди без блокировки. Основная идея состоит в том, чтобы блокировать после спина, продолжать конкурировать за замки после переключения конкуренции, слегка жертвуя справедливостью, но получая высокую пропускную способность. Когда меньше потоковых конфликтов, аналогичная производительность может быть получена; Когда возникают серьезные конфликты ниток, производительность намного выше, чем у CAS.
Внедрение параллельного пакета:
Поскольку Java's CAS имеет как семантику памяти для нестабильного чтения, так и нестабильной записи, теперь есть четыре способа общения между нитьми Java:
1. Резьба А записывает летучую переменную, а затем поток B считывает летучую переменную.
2. резьба A записывает летучую переменную, а затем поток B использует CAS для обновления летучей переменной.
3. Торд A использует CAS для обновления изменчивой переменной, а затем поток B использует CAS для обновления этой нестабильной переменной.
4. Торд A использует CAS для обновления изменчивой переменной, а затем поток B считывает эту летучую переменную.
Java CAS использует эффективные атомные инструкции на уровне машинного уровня, представленные на современных процессорах, которые выполняют операции с чтениями-фарритом на атомической памяти, что является ключом к достижению синхронизации в многопроцессорах (по сути, компьютерная машина, которая может поддерживать атомные инструкции по изменению чтения. Выполните атомные операции чтения-варита в памяти). В то же время, чтение/запись и CAS летучей переменной могут реализовать связь между потоками. Интеграция этих функций вместе образует краеугольный камень реализации всего параллельного пакета. Если мы тщательно проанализируем реализацию исходного кода параллельного пакета, мы найдем общую шаблон реализации:
1. Во -первых, объявить общую переменную быть изменчивой;
2. Затем используйте обновление CAS атомного условия для достижения синхронизации между потоками;
3. В то же время, связь между потоками достигается с помощью чтения/записи летучих и семантики памяти летучего чтения и записи в CAS.
AQS, не блокирующие структуры данных и классы атомных переменных (классы в пакете java.util.concurrent.atomic), основные классы в этих параллельных пакетах реализованы с использованием этого шаблона, а классы высокого уровня в одновременном пакете полагаются на эти базовые классы для реализации. С общей точки зрения, диаграмма реализации параллельного пакета заключается в следующем:
CAS (назначение объектов в куче):
Java вызывает new object() для создания объекта, который будет выделен на кучу JVM. Так как этот объект сохраняется в куче?
Прежде всего, когда выполняется new object() , сколько места требуется этот объект, на самом деле определяется, потому что различные типы данных в Java и сколько места они занимают, если вам не ясно о его принципе, пожалуйста, Google It Yoursers). Затем следующая задача - найти кусок места в куче, чтобы хранить этот объект.
В случае единого потока, как правило, есть две стратегии распределения:
1. Столкновение указателя: это обычно применимо к памяти, которая абсолютно регулярно (независимо от того, является ли память регулярной, зависит от стратегии утилизации памяти). Задача выделения пространства состоит в том, чтобы просто перемещать указатель, как расстояние размера объекта на стороне свободной памяти.
2. Свободный список: это подходит для нерегулярной памяти. В этом случае JVM сохранит список памяти, чтобы записать, какие области памяти бесплатны и какой размер. При распределении пространства на объекты перейдите в свободный список, чтобы запросить соответствующую область, а затем распределить его.
Тем не менее, JVM не может работать в одном резьбовом состоянии постоянно, поэтому эффективность слишком плохая. Поскольку это не атомная операция при распределении памяти на другой объект, по крайней мере требуются следующие шаги: поиск свободного списка, распределение памяти, изменение свободного списка и т. Д., Что не является безопасным. Есть также две стратегии для решения проблем безопасности во время параллелистики:
1. CAS: Фактически, виртуальная машина использует CAS для обеспечения атомальности операции обновления, не ударив повторно, а принцип такой же, как указано выше.
2. TLAB: Если CAS используется, он фактически окажет влияние на производительность, поэтому JVM предложила более продвинутую стратегию оптимизации: каждый поток предварительно выделяет небольшой кусочек памяти в куче Java, называемом локальным буфером выделения потока (TLAB). Когда поток должен выделить память внутри него, ее достаточно, чтобы распределить ее непосредственно на TLAB, избегая конфликтов потока. Только когда буферная память используется и необходимо перераспределять память, будет выполнена операция CAS для распределения большего пространства памяти.
Использует ли виртуальная машина TLAB, можно настроить через параметр -XX:+/-UseTLAB (JDK5 и более поздние версии включены по умолчанию).
Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.