In Java Concurrency in our initial contact should be the synchronized
key, but synchronized
belongs to the heavyweights lock, often causes performance problems, volatile
is also a good choice, but volatile
can not guarantee atomicity, can only be used in some cases.
Like synchronized
this exclusive lock belongs pessimistic lock , it is surely on the assumption that conflict, then the lock just useful addition, there are optimistic locking , optimistic locking meaning is the assumption that there is no conflict, then I can just be an action, if if the conflict does, I'll retry until successful, optimistic locking is the most common CAS
.
When we read the source category under the Concurrent package, I found that both the atom inside a class ReenterLock AQS, or a variety of Atomic beginning , are applied to the interior CAS
, the most common is that we encountered in concurrent programming i++
situation. The traditional method is certainly on the plus method synchronized
Keywords:
public class Test {
public volatile int i;
public synchronized void add() {
i++;
}
}
复制代码
However, this method might almost, we can also use in performance AtomicInteger
, you can ensure i
atoms ++
of.
public class Test {
public AtomicInteger i;
public void add() {
i.getAndIncrement();
}
}
复制代码
We look getAndIncrement
inside:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
复制代码
And then deep into the 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;
}
复制代码
Here we see compareAndSwapInt
this function, it is also the CAS
abbreviation of the origin. So careful analysis of this function to do anything at all?
First, we found that compareAndSwapInt
the previous this
, then it belongs to which category it, we spotted a step getAndAddInt
, in front unsafe
. Here we enter the Unsafe
class. Here we must to Unsafe
be a description of the class. Combined with AtomicInteger
the definition of terms:
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;
...
复制代码
In the AtomicInteger
portion of the data definition, we can see that, in fact, the actual value is stored in value
the, in addition we also get the unsafe
instance, and defines valueOffset
. To see static
the block, to understand the class loading process knows static
when loading blocks occur in class loading, it is initialized first, we call this time unsafe
of objectFieldOffset
the Atomic
class file to obtain value
the offset, then valueOffset
in fact recorded value
biased shift amount.
Back to the top of a function getAndAddInt
, we look at var5
what is obtained by calling unsafe
the getIntVolatile(var1, var2)
, this is a native method, to achieve specific source JDK go looked, in fact, get var1
, the var2
value at offset. var1
That is AtomicInteger
, var2
that we mentioned earlier valueOffset
, so that we get from memory to the current valueOffset
value at the.
Now focus here, compareAndSwapInt(var1, var2, var5, var5 + var4)
in fact, replaced by compareAndSwapInt(obj, offset, expect, update)
more clearly, meaning that if the obj
inside value
and expect
equal, as evidenced by no other thread changed this variable, then update it as update
if this step is CAS
not successful, then continue using the spin of the way for CAS
operation, which is two steps removed first glance, ah, in fact, JNI
there is a means of the CPU
instruction is completed. They still atomic operation.
The underlying principle of CAS
CAS underlying use JNI
to call C code to achieve, if you have Hotspot
the source code, then Unsafe.cpp
you can find in its implementation:
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)},
//省略一堆代码...
};
复制代码
We can see compareAndSwapInt achieved in the Unsafe_CompareAndSwapInt
inside, and then down to 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 is an object taken, p addr is the address of the offset at the last call Atomic::cmpxchg(x, addr, e)
, where x is the parameter value will be updated, the parameter e is the value of the original memory. Code can be seen cmpxchg have achieved various platforms based on where I choose to source code in Linux X86 platform analysis:
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;
}
复制代码
This is a small compilation of __asm__
note is a compilation ASM, __volatile__
prohibit compiler optimizations
// 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
Determining whether the current system is a multi-core system, if the bus is locked give, so other processors on the same chip temporarily can not access the memory bus, the instruction is guaranteed atomicity in a multiprocessor environment.
Before the official interpretation of this compilation, we embed compiled under the basic format to understand:
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: list of clobbered registers /* optional */
);
复制代码
-
template that
cmpxchgl %1,(%3)
represents a compilation template -
output operands represent output operand,
=a
corresponding to the eax register -
input operand represents the input parameters,
%1
that isexchange_value
,%3
isdest
,%4
that ismp
,r
represents any register,a
oreax
register -
list of clobbered registers is some additional parameters
cc
that the compilercmpxchgl
execution will affect the flag registermemory
tells the compiler to the latest value of the variable to be re-read from the memory, which points to achieve avolatile
feeling.
Then the expression in fact cmpxchgl exchange_value ,dest
, we find %2
that is compare_value
not to spend, here is to analyze cmpxchgl
the semantics of. cmpxchgl
End l
represents the length of the operand 4
, the above has been known. cmpxchgl
The default will compare eax
the value of the register that is, compare_value
and exchange_value
values, if they are equal, put the dest
value assigned to exchange_value
, otherwise, will be exchange_value
assigned toeax
. Specific assembly instructions can view the Intel manual CMPXCHG
Finally, by the CPU of the JDK cmpxchgl
supports instructions, to achieve AtomicInteger
the CAS
atomic operation.
CAS issues
- ABA problem
CAS needs to have time to check the operating values lower value has not changed, if not changed is updated, but if a value is A, became a B, he is a A, you'll find it when using CAS inspection the value does not change, but actually changed. This is the ABA problem CAS. Common Solutions is a version number. Append the version number in front of variables, each variable is updated when the version number plus one, then A-B-A
it will become 1A-2B-3A
. Currently it offers a class in the JDK atomic package AtomicStampedReference
to solve the ABA problem. The method of action of this class is compareAndSet checks the current reference is equal to the expected reference and the current mark is equal to the expected flag, if all equal Atomically the reference value and the flag is set to the given updated value.
- Large overhead long cycle time
Above we said that if CAS is not successful, it will spin in place, if a long spin will bring a very large CPU execution cost.
Author: Kabbalah Tree
link: https: //juejin.im/post/5a73cbbff265da4e807783f5
Source: Nuggets
copyright reserved by the authors. Commercial reprint please contact the author authorized, non-commercial reprint please indicate the source.