AtomicInteger
getAndSet:设置为新值,返回设置之前的值。此方法为原子性操作,即保证在得到当前值与设置新值之间,没有任何其他更新操作。
|
public
final
int
getAndSet
(
int
newValue
)
{
for
(
;
;
)
{
int
current
=
get
(
)
;
if
(
compareAndSet
(
current
,
newValue
)
)
return
current
;
}
}
|
getAndIncrement:当前值加一,返回自增前的值。此方法为原子性操作,即保证在得到当前值与自增 之间,没有任何其他更新操作。举个例子,假如是value++,两个线程同时调用此函数,两个线程都取到了当前值100,然后都基于100加一,最后得到 101,miss掉了一次自增。采用compareAndSet,两个线程同时取到当前值100,线程一成功调用compareAndSet,当前值变为 101,返回;线程二调用compareAndSet失败,回到循环头,重新取得一个新的当前值101,接着成功调用comareAndSet,更新当前 值为102,返回
|
public
final
int
getAndIncrement
(
)
{
for
(
;
;
)
{
int
current
=
get
(
)
;
int
next
=
current
+
1
;
if
(
compareAndSet
(
current
,
next
)
)
return
current
;
}
}
|
compareAndSet:如果当前值与期望值(第一个参数)相等,则设置为新值(第二个参数),设置成功返回true。
|
public
final
boolean
compareAndSet
(
int
expect
,
int
update
)
{
return
unsafe
.
compareAndSwapInt
(
this
,
valueOffset
,
expect
,
update
)
;
}
|
Unsafe
ObjectFieldOffset:前文中出现的参数valueOffset是一个静态变量,调用Unsafe.objectFieldOffset得到
|
private
static
final
long
valueOffset
;
static
{
try
{
valueOffset
=
unsafe
.
objectFieldOffset
(
AtomicInteger
.
class
.
getDeclaredField
(“
value”
)
)
;
}
catch
(
Exception
ex
)
{
throw
new
Error
(
ex
)
;
}
}
|
在java对象的地址一文中说这个地址不是对象在内存中的物理地址,只是一个逻辑地址。javadoc中注释这是属性(field)在对象存储空间中的偏移地址。
compareAndSet:如果当前值与期望值相等,更新对象的一个整数属性(field)为给定的值。更新则返回true,否则为false。此方法为原子性操作,而且是native方法。
|
public
native
boolean
compareAndSwapInt
(
Object
obj
,
long
offset
,
int
expect
,
int
update
)
;
|
UnSafe native
在Java5 Concurrent包中的锁机制中,UnSafe的native代码从Kaffe(java 虚拟机的首个开源实现)中扒了出来。
首先得到属性(field)的指针,然后调用g_atomic_int_compare_and_exchange方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* Helper macro, defining a sun.misc.Unsafe compare and swap function
* with a given NAME tail and TYPE of arguments.
*/
#define KAFFE_UNSAFE_COMPARE_AND_SWAP(NAME, TYPE)
JNIEXPORT
jboolean
JNICALL
Java_sun_misc_Unsafe_compareAndSwap
## NAME(JNIEnv* env, jobject unsafe UNUSED, jobject obj, jlong offset, TYPE expect, TYPE update)
{
volatile
TYPE
*
address
=
getFieldAddress
(
env
,
obj
,
offset
)
;
if
(
sizeof
(
TYPE
)
==
sizeof
(
gint
)
)
return
g_atomic_int_compare_and_exchange
(
(
volatile
gint
*
)
address
,
(
gint
)
expect
,
(
gint
)
update
)
;
else
if
(
sizeof
(
TYPE
)
==
sizeof
(
gpointer
)
)
return
g_atomic_pointer_compare_and_exchange
(
(
volatile
gpointer
*
)
address
,
(
gpointer
)
expect
,
(
gpointer
)
update
)
;
else
if
(
*
address
==
expect
)
{
*
address
=
update
;
return
JNI_TRUE
;
}
else
return
JNI_FALSE
;
}
|
GLIB
g_atomic_pointer_compare_and_exchange是glib中的一段c代码
|
gboolean
g_atomic_int_compare_and_exchange
(
volatile
gint
*
atomic
,
gint
oldval
,
gint
newval
)
{
gint
result
;
__asm__
__volatile__
(“
lock
;
cmpxchgl
%
2
,
%
1″
: ”
=
a”
(
result
)
, ”
=
m”
(
*
atomic
)
: ”
r”
(
newval
)
, ”
m”
(
*
atomic
)
, ”
0″
(
oldval
)
:
)
;
return
result
==
oldval
;
}
|
GCC 内联汇编
汇编指令cmpxchg:上面的函数中嵌入了汇编语句(c代码中嵌入汇编,称为内联汇编),调用了一条汇编指令cmpxchgl(AT&T风格),对应Intel格式的汇编指令为cmpxchg。
根据古月今人的解释:cmpxchg是将寄存器(AL, AX或者EAX,取决于值的大小)中的值和目标操作数(第一个参数)进行比较,如果相等,则将源操作数(第二个参数)拷贝到目标操作数中,同时ZF置1;否则ZF置0,将目标操作数拷贝到EAX。
仿照百度blog的一篇文章,给出伪码为
|
%
eax
=
old
;
if
(
*
address
==
%
eax
)
{
*
address
=
new
;
zf
=
1
;
return
%
eax
;
}
else
{
zf
=
0
;
return
*
address
;
}
|
关于寄存器为什么叫AL/AH, AX, EAX(extend AX),请参考x86的寄存器
内联汇编
GCC扩展内联汇编语法格式为:
|
asm
(
assembler
template
:
output
operands
/* optional */
:
input
operands
/* optional */
:
list
of
clobbered
registers
/* optional */
)
;
|
前文中”lock; cmpxchgl %2, %1″是汇编模板,指定汇编指令为cmpxchgl(AT&T格式),%2为源操作数,%1为目标操作数。
“=a”(result), “=m”(*atomic) 是输出参数。
“r”(newval), “m”(*atomic), “0″(oldval)是输入参数。参数前面的双引号内容表示对参数的限制,
- “r”是将参数放入寄存器中
- “=”表示write-only,之前的值直接被替换为新值
- “a”表示eax寄存器
- “m”表示直接操作内存,而不是通过寄存器
- “0“表示使用和内联汇编中第一个操作数相同的寄存器,这里是和result使用相同的寄存器(eax)
上面的内联汇编
- 将oldval送到eax寄存器中
- 调用汇编指令cmpxchgl,newval和*atomic作为其源操作数和目标操作数
- 将汇编指令调用的结果(在寄存器eax中)返回给result。*atomic有可能被更新。
加上LOCK前缀,保证原子性;否则要将类似前文java中,循环重试直到更新成功。
Volatile则避免编译器优化,打乱顺序(reorder)。
总结
java中的CAS调用底层的CAS操作。
AtomicInteger -> Unsafe -> Unsafe c 代码 -> 内联汇编 -> 汇编