示例
val atomicInteger = AtomicInteger(10)
atomicInteger.decrementAndGet()// atomicInteger--
- 从静态代码块可以看到,在类初始化的时候拿到值的偏移量(获取偏移量后直接操作内存,从内存取值,不通过缓冲区)。在构造函数保存初始值,并且是volatile修饰
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
private static final long VALUE;
static {
try {
VALUE = U.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
- 查看decrementAndGet源码
public final int decrementAndGet() {
return U.getAndAddInt(this, VALUE, -1) - 1;
}
- U是sun.misc.Unsafe类里面的,找到Unsafe里面的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;
}
- var1就是atomic对象,var2就是偏移量,var4就是要做的减1。var5是通过对象起始地址偏移VALUE的距离,拿到9(示例代码设置的9),是从主内存拿的。
- compareAndSwapInt就是 CAS 机制
- 首先计算var5 + var4的值,然后存储一份,然后再通过var1, var2在内存中拿到atomic的值,对比拿到的和var5 + var4的值是否相等,如果相等就允许var5减一(var5与var5 + var4交换)然后返回,如果不等就一直循环判断。
- 在单线程中没有意义反而变复杂了
- 假如有AB两个线程,A线程在执行var5 + var4的操作之后时间片段用完了,切换到B线程,B线程已经走完了所有流程,值已经被修改为9。再切换到A线程的时候接着执行通过var1, var2在内存中拿到atomic的值,对比拿到的和var5 + var4的值是否相等就会发现不相等了,因为已经被修改了,然后重新执行do循环里面的操作,重新在内存取值,取到9,然后判断是相等的,也就跳出循环了。
- 对比拿到的和var5 + var4的值是否相等和对var5赋值是有CPU保证操作是原子性的
- 这也是乐观锁
- 没有通过复杂的机制来阻塞线程,并且达到目的,比加锁的代码效率提高很多
- 但是只适合少量并发中使用,高并发中会导致CPU占用率高。
-
这种乐观锁(无锁)机制还有一个ABA问题。假如操作的是引用类型,在AB两个线程中,一开始对象是a,在循环中间把对象换成了b,然后又换成了a,对比结果还是a,但是其实中间做了很多其他事情,在并发中可能会导致严重问题
CAS.jpg
网友评论