Концепция без блокировки была упомянута во введении [высокой параллельной Java 1]. Поскольку в исходном коде JDK существует большое количество приложений без блокировки, здесь вводится без блокировки.
1 Подробное объяснение принципа беззаколого класса
1.1 CAS
Процесс алгоритма CAS выглядит следующим образом: он содержит 3 параметра CAS (V, E, N). V представляет собой переменную, которая должна быть обновлена, E представляет ожидаемое значение, а N представляет новое значение. Только если v
Когда значение равно значению E, значение V будет установлено на N. Если значение V отличается от значения E, это означает, что другие потоки уже сделали обновления, а текущий поток ничего не делает. Наконец, CAS возвращает истинное значение текущих операций V. CAS, выполняемых с оптимистичным отношением, и всегда считает, что он может успешно завершить операции. Когда несколько потоков работают с переменной, используя CAS одновременно, только один выиграет и успешно обновит, а остальные потерпят неудачу. Неудачный поток не будет приостановлен, только сказано, что сбой разрешена, и ему разрешено повторить попытку, и, конечно, неудачный поток также позволит отказаться от операции. На основании этого принципа CAS
Операция немедленно заблокирована, и другие потоки могут также обнаружить помехи в текущий поток и обрабатывать его соответствующим образом.
Мы обнаружим, что в CAS слишком много шагов. Возможно ли, что после оценки того, что V и E одинаковы, когда мы собираемся назначить значения, мы переключили поток и изменили значение. Что вызвало несоответствие данных?
На самом деле, это беспокойство избыточно. Весь процесс работы CAS - это атомная операция, которая завершается инструкцией процессора.
1.2 Инструкции процессора
Инструкция CPU CAS - CMMPXCHG
Код инструкции заключается в следующем:
/ * accumulator = al, ax или ax, в зависимости от того, выполняется ли сравнение байта, слова или двойного слова */ if (accumulator == destination) {zf = 1; Пункт назначения = источник; } else {zf = 0; Accumulator = пункт назначения; } Если целевое значение равно значению в реестре, установлен флаг прыжка, и исходные данные устанавливаются на цель. Если вы не ждете, вы не установите флаг прыжка.
Java предоставляет много классов без блокировки, поэтому давайте представим классы без блокировки ниже.
2 бесполезно
Мы уже знаем, что без блокировки гораздо эффективнее, чем блокировка. Давайте посмотрим, как Java реализует эти классы без блокировки.
2.1. Atomicinteger
AtomicInteger, как и целое число, оба наследуют номерной класс
Public Class AtomicInteger расширяет числовые реализации java.io.serializable
В AtomicInteger есть много операций CAS, типичные из которых:
Public Final Boolean Compareandeset (int ожидайте, int update) {
вернуть uncafe.compareandswapint (это, valueOffset, ожидайте, обновление);
}
Здесь мы объясним метод небезопасного. Это означает, что если значение переменной, чье смещение в этом классе является valueOffet, такое же, как и ожидаемое значение, то установите значение этой переменной для обновления.
На самом деле, переменная с смещенным значением - это значение
static {try {valueOffset = uncafe.objectfieldoffset (atomicinteger.class.getDeclaredfield ("value")); } catch (Exception ex) {бросить новую ошибку (ex); }}Ранее мы сказали, что CAS может потерпеть неудачу, но стоимость сбоя очень мала, поэтому общая реализация находится в бесконечном цикле, пока она не преуспеет.
public final int getAndIncrement () {for (;;) {int current = get (); int next = current + 1; if (CompareAndset (current, Next)) возврат ток; }}2.2 Небезопасно
Из названия класса мы видим, что небезопасные операции являются небезопасными операциями, такие как:
Установите значение в соответствии с смещением (я видел эту функцию в Atomicinteger.
Park () (остановите эту ветку, она будет упомянута в будущем блоге)
Базовая операция CAS, непубличная API, может сильно различаться в разных версиях JDK.
2.3. AtomicReference
AtomicInteger упоминался ранее, и, конечно, атомство, атомно -атомно и т. Д. Все схожи.
То, что мы хотим представить здесь, это AtomicReference.
AtomicReference - это шаблонный класс
AtomicReference public Class <v> реализует java.io.serializable
Его можно использовать для инкапсуляции любого типа данных.
Например, строка
Пакет Test; Import java.util.concurrent.atomic.atomicReference; открытый тест класса {public final Static AtomicReference <string> atomicString = new AtomicReference <string> ("Hosee"); public static void main (string [] args) {for (int i = 0; i <10; i ++) {final int num = i; new Thread () {public void run () {try {thread.sleep (math.abs ((int) math.random ()*100)); } catch (Exception e) {e.printstackTrace (); } if (atomicstring.compareanddset ("hosee", "ztk")) {system.out.println (thread.currentthread (). getId () + "значение изменения"); } else {System.out.println (thread.currentThread (). getId () + "не удалось"); }}; }.начинать(); }}}результат:
10failed
13failed
Значение 9change
11failed
12failed
15failed
17 заканчивается
14failed
16failed
18failed
Вы можете видеть, что только один поток может изменить значение, и последующие потоки не могут больше его модифицировать.
2.4. AtomicStampedReference
Мы обнаружим, что все еще есть проблема с работой CAS.
Например, предыдущий метод AtomicInteger IncrementAndget
public final int ingrementAndget () {for (;;) {int current = get (); int next = current + 1; if (CompareAndset (current, Next)) return далее; }} Предположим, что текущее значение = 1, когда поток int current = get () выполняется, переключитесь на другой поток, этот поток превращает 1 на 2, а затем другой поток снова превращается в 2 в 1. В настоящее время переключитесь на начальный поток. Поскольку значение по -прежнему равно 1, операции CAS все еще могут быть выполнены. Конечно, нет проблем с дополнением. Если есть некоторые случаи, такой процесс не будет разрешен, когда он чувствителен к состоянию данных.
В настоящее время требуется класс AtomicStampedReference.
Он внедряет пара класса внутри, чтобы инкапсулировать значения и временные метки.
Частная статическая пара классов <t> {final t Ссылка; окончательный int mamp; Private Pair (t Ссылка, int mamp) {this.reference = reward; this.Stamp = Stamp; } static <t> pair <t> of (t stragher, int mamp) {return new pare <t> (ссылка, Stamp); }}Основная идея этого класса состоит в том, чтобы добавить временные метки, чтобы определить каждое изменение.
// Сравнение параметров параметров: ожидаемое значение записывает новое значение.
Public Boolean CompareAndset (v wederreference, v newReference, int wearsstamp, int newStamp) {pair <v> current = pair; return wederreference == current.reference && wederstamp == current.stamp && ((newReference == current.Reference && newStamp == current.stamp) || caspair (current, pare.of (newReference, newStamp))); } Когда ожидаемое значение равно текущему значению, а ожидаемая метка времени равна текущей метке времени, новое значение записывается, и новая метка времени обновляется.
Вот сценарий, который использует AtomicStampedReference. Это может быть не подходит, но я не могу представить себе хороший сценарий.
Фон сцены заключается в том, что компания заряжает пользователей с низким балансом бесплатно, но каждый пользователь может перезарядиться только один раз.
Пакет Test; Import java.util.concurrent.atomic.atomicStampedReference; Общедоступный тест {статический AtomicStampedReference <Integer> money = new AtomicStampedReference <Integer> (19, 0); public static void main (string [] args) {for (int i = 0; i <3; i ++) {final int timestamp = money.getStamp (); new Thread () {public void run () {while (true) {while (true) {integer m = money.getReference (); if (m <20) {if (money.compareandset (m, m + 20, timestamp, timeStamp + 1)) {System.out.println ("Пополнить успешно, баланс:" + money.getReference ()); перерыв; }} else {break; }}}}}}; }.начинать(); } new Thread () {public void run () {for (int i = 0; i <100; i ++) {while (true) {int timeStamp = money.getStamp (); Integer m = money.getReference (); if (m> 10) {if (money.compareandset (m, m - 10, timestamp, timestamp + 1)) {System.out.println («Потребляется 10 юаней, баланс:» + money.getReference ()); перерыв; }} else {break; }} try {thread.sleep (100); } catch (Exception e) {// toDo: обрабатывать исключение}}}}; }.начинать(); }}Объясните код, есть 3 потока, перезаряжающих пользователя. Когда баланс пользователя составляет менее 20, заряжайте пользователя 20 юаней. Есть 100 потоков, каждый из которых тратит 10 юаней. Пользователь первоначально имеет 9 юаней. При использовании AtomicStampedReference для его реализации пользователь будет перезаряжен только один раз, потому что каждая операция вызывает метку времени +1. Результаты работы:
Пополнить успешно, баланс: 39
Потребление 10 юаней, баланс: 29
Потребление 10 юаней, баланс: 19
Потребление 10 юаней, баланс: 9
Если вы используете AtomicReference <Integer> или Atomic Integer для его реализации, это вызовет множественные пополнения.
Пополнить успешно, баланс: 39
Потребление 10 юаней, баланс: 29
Потребление 10 юаней, баланс: 19
Пополнить успешно, баланс: 39
Потребление 10 юаней, баланс: 29
Потребление 10 юаней, баланс: 19
Пополнить успешно, баланс: 39
Потребление 10 юаней, баланс: 29
2.5. Atomicintegerarray
По сравнению с AtomicInteger, реализация массивов является лишь дополнительным индексом.
Public Final Boolean Compareandeset (int i, int weard, int update) {
return CompareAndsetraw (checkedByteOffset (i), ожидайте, обновление);
}
Его интерьер просто инкапсулирует нормальный массив
Частный финал int [] массив;
Что интересно здесь, так это то, что ведущие нули бинарных чисел используются для расчета смещения в массиве.
Shift = 31 - Integer.NumberOfLeadingZeros (Scale);
Ведущий ноль означает, что, например, 8 бит представляют 12 00001100, то ведущий ноль - это число 0 перед 1, то есть 4.
Как рассчитать смещение, здесь не представлено.
2.6 Atomicintegerfieldupdater
Основная функция класса Atomicintegerfieldupdater - позволить обычным переменным также пользоваться атомными операциями.
Например, изначально была переменная, которая была типом Int, и эта переменная была применена во многих местах. Однако в определенном сценарии, если вы хотите превратить тип Int в AtomicInteger, если вы измените тип напрямую, вы должны изменить приложение в других местах. Atomicintegerfieldupdater предназначен для решения таких проблем.
Пакет Test; import java.util.concurrent.atomic.atomicinteger; import java.util.concurrent.atomic.atomicintegerfieldupdater; Общедоступный тест {public Static V {int id; нестабильный балл; public int getScore () {return Score; } public void setScore (int chold) {this.score = comp; }} public final Static Atomicintegerfieldupdater <v> vv = atomicintegerfieldupdater.newupdater (v.class, "Score"); Public Static Atomicinteger AllScore = новый AtomicInteger (0); public static void main (string [] args) бросает прерывание {final v stu = new v (); Thread [] t = новый поток [10000]; for (int i = 0; i <10000; i ++) {t [i] = new Thread () {@Override public void run () {if (math.random ()> 0.4) {vv.incrementAndget (stu); allscore.incrementAndget (); }}}; t [i] .start (); } for (int i = 0; i <10000; i ++) {t [i] .join (); } System.out.println ("score ="+stu.getScore ()); System.out.println ("allScore ="+allScore); }} Приведенный выше код превращает оценку с использованием Atomicintegerfieldupdater в Atomicinteger. Обеспечить безопасность потока.
AllScore используется здесь для проверки. Если значения оценки и AllScore одинаковы, это означает, что он безопасен для потока.
Примечание: