synchronized
ключевое слово, ноsynchronized
Он принадлежит к тяжеловесному замке, что приведет к тому, что проблемы с производительностью много раз.volatile
тоже хороший выбор, ноvolatile
Атомарность не гарантируется и может использоваться только в определенных ситуациях.
рисунокsynchronized
Этот эксклюзивный замок принадлежитпессимистический замок, предполагается, что должен быть конфликт, тогда блокировка оказывается полезной, кроме того, естьоптимистическая блокировка, смысл оптимистической блокировки заключается в том, чтобы предположить, что конфликта нет, тогда я могу просто выполнить операцию. Если есть конфликт, то я буду повторять попытку до тех пор, пока она не увенчается успехом. Наиболее распространенная оптимистическая блокировкаCAS
.
Когда мы прочитали исходный код классов в пакете Concurrent, мы обнаружили, что либоAQS внутри ReenterLock также является атомарным классом в начале различных атомарных, которые применяются внутрьCAS
, чаще всего это то, с чем мы сталкиваемся в параллельном программированииi++
Эта ситуация. Традиционный метод определенно добавляет методsynchronized
Ключевые слова:
public class Test {
public volatile int i;
public synchronized void add() {
i++;
}
}
Но этот метод может быть немного хуже по производительности, мы также можем использоватьAtomicInteger
, можно гарантироватьi
атомный++
.
public class Test {
public AtomicInteger i;
public void add() {
i.getAndIncrement();
}
}
ПосмотримgetAndIncrement
внутренний:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
идти глубжеgetAndAddInt
():
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
Здесь мы видимcompareAndSwapInt
эта функция, она такжеCAS
Происхождение аббревиатуры. Итак, внимательно проанализируйте, что делает эта функция?
Сначала мы находимcompareAndSwapInt
фронтthis
, то к какому классу он относится, давайте посмотрим на предыдущий шагgetAndAddInt
, спередиunsafe
. Здесь мы входимUnsafe
своего рода. Прямо здесьUnsafe
класс объяснить. комбинироватьAtomicInteger
Значение:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
...
существуетAtomicInteger
В части определения данных мы видим, что фактическое сохраненное значение помещается вvalue
, кроме того, мы также получаемunsafe
экземпляр и определяетvalueOffset
. увидеть сноваstatic
блок, знает каждый, кто разбирается в процессе загрузки класса,static
Загрузка блока происходит, когда класс загружается и инициализируется первым, в это время мы вызываемunsafe
изobjectFieldOffset
отAtomic
файл классаvalue
смещение, затемvalueOffset
это вообще рекордvalue
смещения.
Вернитесь к функции вышеgetAndAddInt
, мы видимvar5
что получается при вызовеunsafe
изgetIntVolatile(var1, var2)
, это нативный метод, конкретную реализацию можно увидеть в исходниках JDK, на самом деле он заключается в полученииvar1
середина,var2
Значение по смещению.var1
этоAtomicInteger
,var2
то, что мы упоминали ранееvalueOffset
, так что мы попадаем из памяти в настоящееvalueOffset
стоимость в.
Теперь наступает момент,compareAndSwapInt(var1, var2, var5, var5 + var4)
На самом деле, замените его наcompareAndSwapInt(obj, offset, expect, update)
Понятно, что еслиobj
внутриvalue
иexpect
равным, это доказывает, что ни один другой поток не изменил эту переменную, а затем обновите ее доupdate
, если этот шагCAS
Если это не сработает, то используйте метод вращения, чтобы продолжитьCAS
Операция, вынесите на первый взгляд, это тоже два шага, на самом деле вJNI
с помощьюCPU
инструкция завершена. Так что это все еще атомарная операция.
Основной принцип CAS
Базовое использование CASJNI
Вызовите реализацию кода C, если у вас естьHotspot
исходный код, затем вUnsafe.cpp
Его реализацию можно найти в:
static JNINativeMethod methods_15[] = {
//省略一堆代码...
{CC"compareAndSwapInt", CC"("OBJ"J""I""I"")Z", FN_PTR(Unsafe_CompareAndSwapInt)},
{CC"compareAndSwapLong", CC"("OBJ"J""J""J"")Z", FN_PTR(Unsafe_CompareAndSwapLong)},
//省略一堆代码...
};
Мы видим, что реализация compareAndSwapInt находится вUnsafe_CompareAndSwapInt
внутри, дальше вUnsafe_CompareAndSwapInt
:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
p — извлеченный объект, addr — адрес со смещением в p, и, наконец, вызываетсяAtomic::cmpxchg(x, addr, e)
, где параметр x — это значение, которое нужно обновить, а параметр e — это значение исходной памяти. По коду видно, что cmpxchg имеет реализации на разных платформах, здесь я выбираю анализ исходного кода под платформу Linux X86:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
Это небольшая подборка,__asm__
Описание сборки ASM,__volatile__
Отключить оптимизацию компилятора
// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
os::is_MP
Определите, является ли текущая система многоядерной, и если да, заблокируйте шину, чтобы другие процессоры на том же кристалле не могли временно получить доступ к памяти через шину, обеспечив атомарность инструкции в многопроцессорной среде.
Прежде чем формально интерпретировать эту сборку, давайте разберемся с основным форматом встроенной сборки:
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);
-
templateэто
cmpxchgl %1,(%3)
Представляет шаблон сборки -
output operandsпредставляет выходной операнд,
=a
Соответствует регистру eax -
input operandпредставляет входной параметр,
%1
этоexchange_value
,%3
даdest
,%4
этоmp
,r
представляет любой регистр,a
все ещеeax
регистр -
list of clobbered registersпросто дополнительные параметры,
cc
означает компиляторcmpxchgl
Выполнение повлияет на регистр флагов,memory
Скажите компилятору перечитать последнее значение переменной из памяти, что реализованоvolatile
ощущение.
Тогда выражение действительноcmpxchgl exchange_value ,dest
, мы найдем%2
этоcompare_value
Если он не используется, он будет проанализирован здесьcmpxchgl
семантика.cmpxchgl
конецl
Указывает, что длина операнда4
, уже известное выше.cmpxchgl
будет сравнивать по умолчаниюeax
Значение регистраcompare_value
иexchange_value
значение ,Если они равны, положитьdest
Значение, присвоенноеexchange_value
, в противном случаеexchange_value
назначить вeax
. Конкретные инструкции по сборке см. в руководстве Intel.CMPXCHG
В конечном счете, JDK проходит через ЦП.cmpxchgl
Реализована поддержка директивAtomicInteger
изCAS
Атомарность операций.
Проблемы с CAS
- АВА-проблема
CAS должен проверить, изменилось ли значение при работе со значением, и обновить его, если изменений нет, но если значение изначально было A, стало B, а затем стало A, то оно будет найдено при использовании CAS для проверки. Значение не изменилось, но оно действительно изменилось. Это проблема ABA CAS.
Распространенным решением является использование номеров версий. Добавьте номер версии перед переменной и увеличивайте номер версии на единицу каждый раз, когда переменная обновляется, затемA-B-A
станет1A-2B-3A
.
В настоящее время класс предоставляется в атомарном пакете JDK.AtomicStampedReference
для решения проблемы АБА. Роль метода compareAndSet этого класса состоит в том, чтобы сначала проверить, равна ли текущая ссылка ожидаемой ссылке, и равен ли текущий флаг ожидаемому флагу, и, если все равны, атомарно установить ссылку и значение флаг на заданное обновленное значение.
- Длительное время цикла и высокие накладные расходы
Выше мы сказали, что если CAS будет неудачным, то он будет крутиться на месте, а если крутится долго, то принесет очень большие накладные расходы на выполнение CPU.