При чтении книги "Practical Java High Concurrency Programming" испытуемый изучил соответствующие концепции Java без блокировок и записал их здесь, чтобы углубить свое понимание. В Java есть два типа блокировок, а именно пессимистические блокировки и оптимистичные блокировки. , так что же такое пессимистическая блокировка и оптимистичная блокировка?Нажмите, чтобы просмотреть исходный текст
Оптимистическая блокировка и пессимистическая блокировка
В нашем коде часто используются пессимистические блокировки, например, в Java.synchronized
иReentrantLock
Эксклюзивная блокировка является реализацией идеи пессимистической блокировки, которая всегда предполагает, что другие потоки будут изменять данные, когда они получат данные, поэтому они будут блокироваться каждый раз, когда они получат данные, так что другие потоки будут заблокированы, когда они захотят. получить данные Блокировать, пока он не получит блокировку.
В отличие от оптимистической блокировки, она всегда предполагает, что другие потоки не будут изменять данные при выборке данных, поэтому она не будет заблокирована, но будет определять, были ли данные обновлены при их обновлении. То есть оптимистическая блокировка (без блокировки) использует технику сравнения свопов (CAS
Compare And Swap
) для выявления конфликтов потоков. После обнаружения конфликта текущая операция повторяется до тех пор, пока конфликт не исчезнет.
По сравнению с блокировками использование сравнительного обмена (CAS) делает код немного сложнее. Но из-за своей неблокирующей природы он по своей природе невосприимчив к проблемам взаимоблокировок, а взаимодействие между потоками гораздо меньше, чем подход, основанный на блокировках. Что еще более важно, безблокировочный метод не имеет абсолютно никаких системных издержек, вызванных конкуренцией блокировок, а также издержек, вызванных частым планированием между потоками, поэтому он имеет лучшую производительность, чем метод на основе блокировок.
реализация оптимистичной блокировки
Одной из реализаций LeTV Lock является алгоритм CAS Процесс алгоритма CAS примерно таков: он содержит три параметра CAS(V, E, N).
-
V
Указывает переменную для обновления -
E
представляет собой ожидаемое значение -
N
представляет новое значение Значение V устанавливается равным N, только если V равно E, в противном случае операция не выполняется (сравнение и замена — атомарная операция). Если значение V и значение E не равны, это означает, что другие потоки изменили значение V, текущий поток ничего не делает и, наконец, возвращает истинное значение текущего V. Операция CAS выполняется с оптимизмом, и она всегда верит, что сможет успешно завершить операцию. Когда несколько потоков используют CAS для одновременного управления переменной, только один из них будет успешно обновляться, а остальные не будут выполняться. Неудавшийся поток не приостанавливается, он просто уведомляется о сбое и может повторить попытку, и, конечно же, отказавший поток может прервать операцию.
Применение оптимистической блокировки в JDK
существуетjava.util.concurrent.atomic
Класс атомарной переменной в пакете реализован с помощью CAS. Давайте сосредоточимся на практическом применении CAS в классе AtomicInteger в пакете. Этот класс предоставляет следующие основные методы и свойства:
- public final incrementAndGet() // добавить 1 к текущему значению, вернуть старое значение
- public final int decrementAndGet() // вычитаем 1 из текущего значения и возвращаем старое значение
- public volatile int value // Текущее фактическое значение объекта AtomicInteger
incrementAndGet()
иdecrementAndGet()
Метод аналогичен, просто смотрим на метод incrementAndGet, JDK1.7 и JDK1.8 реализуютincrementAndGet()
способ другой(Улучшение CAS в Java8), ниже приведена реализация в java8, вы можете видеть, что incrementAndGet() на самом деле вызываетsun.misc.Unsafe.getAndAddInt
метод, класс Unsafe можно понимать как указатель в Java, но мы не можем использовать его напрямую, потому что он загружается загрузчиком классов Bootstrap, а не AppLoader.
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1; //
}
в кодеvalueOffset
Представляет смещение поля значения в объекте AtomicInteger (смещение к заголовку объекта), что удобно для быстрого поиска поля.
public final int getAndAddInt(Object obj, long l, int i)
{
int j;
do
j = getIntVolatile(obj, l);
while(!compareAndSwapInt(obj, l, j, j + i));
return j;
}
В метод getAndAddInt передаются следующие параметры: obj (объект AtomicInteger), l (смещение в объекте), i (увеличение значения), вы можете видеть, что getAndAddInt на самом деле является циклом, только когда compareAndSwapInt возвращает значение true, цикл может завершиться и вернутьсяj
(Старое значение), ниже приведена сигнатура метода compareAndSwapInt, где первые два параметра совпадают с параметрами, переданными в метод getAndAddInt, последнее значение — старое значение, полученное с помощью getIntVolatile, а x — новое значение. который вы хотите установить.
public final native boolean compareAndSwapInt(Object obj, long offset, int expected, int x);
Подобно методу compareAndSwapInt, getIntVolatile() также использует атомарные операции для получения значения объекта AtomicInteger Ниже приведена сигнатура метода.
public native int getIntVolatile(Object obj, long l);
CAS широко используется в исходном коде JDK, остальные классы без блокировки приведены ниже:
-
AtomicReference
ссылка на объект без блокировки -
AtomicStampedReference
Ссылка на объект с флагами -
AtomicIntegerArray
массив без блокировок -
AtomicIntegerFieldUpdater
простая переменная без блокировки
проблема с оптимистичной блокировкой
АВА-проблема
Если переменная V читается как значение A в первый раз, и это также значение A, когда оно готово к присвоению, может ли это означать, что значение A не было изменено? На самом деле это невозможно, потому что переменная V может быть изменена другими потоками обратно на значение A. В результате операция CAS ошибочно подумает, что она никогда не изменялась, и затем присвоит ее V.
JDK 1.5 и более поздние версии предоставляют вышеупомянутыеAtomicStampedReference类
Чтобы решить эту проблему, метод compareAndSet сначала проверит, равна ли текущая ссылка ожидаемой ссылке, а затем проверит, равен ли текущий флаг ожидаемому флагу, и если оба равны, то и ссылка, и флаг будут атомарно установлено новое значение.
долгое время отжима
CAS spin — это цикл do-while внутри упомянутого выше метода getAndAddInt().Если compareAndSwapInt не был успешно установлен, цикл do-while продолжается, что приведет к очень большим накладным расходам процессора. Метод выполнения, указанный в Интернете, выглядит следующим образом, непроверенный (еще не пробовал ~)
Если JVM может поддерживать инструкцию паузы, предоставляемую процессором, эффективность будет в определенной степени повышена.Инструкция паузы имеет две функции.Во-первых, она может задерживать инструкцию выполнения конвейера (de-pipeline), так что ЦП не потреблять слишком много ресурсов исполнения.Время задержки зависит от конкретной версии реализации, на некоторых процессорах время задержки равно нулю. Во-вторых, он может избежать очистки конвейера ЦП, вызванной нарушением порядка памяти (нарушением порядка памяти) при выходе из цикла, тем самым повышая эффективность выполнения ЦП.
Гарантируется только одна общая переменная
Операция CAS полезна только для одной общей переменной, и CAS нельзя использовать, когда задействовано несколько переменных.Кроме того, после JDK 1.5 предоставляется ссылка на объект AtomicReference, и в объект AtomicReference можно поместить несколько переменных.
сцены, которые будут использоваться
Короче говоря, CAS подходит для того, чтобы писать меньше (многократные сценарии чтения, как правило, меньше конфликтов), а синхронизированный подходит для того, чтобы писать больше (многократные сценарии записи, как правило, больше конфликтов).
Справочная документация
- Практическое программирование с высокой степенью параллелизма на Java
- Оптимистическая блокировка и пессимистическая блокировка
- Улучшение CAS в Java8