像synchronized
这种独占锁属于悲观锁,它是在假设一定会发生冲突的,那么加锁恰好有用,除此之外,还有乐观锁,乐观锁的含义就是假设没有发生冲突,那么我正好可以进行某项操作,如果要是发生冲突呢,那我就重试直到成功,乐观锁最常见的就是CAS
。
我们在读Concurrent包下的类的源码时,发现无论是ReenterLock内部的AQS,还是各种Atomic开头的原子类,内部都应用到了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();}
}复制代码
CAS源码分析
获取偏移量valueOffset,
public native long objectFieldOffset(Field var1);通过这个方法可以知道偏移量从jdk底层源码中获取。
static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }
}复制代码
然后再看看增加的方法
public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);
}复制代码
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;
}复制代码
我们看
var5
获取的是什么,通过调用unsafe的getIntVolatile(var1, var2)
,这是个native方法,具体实现到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
操作
private volatile int value;和unsafe.getAndAddInt(this, valueOffset, delta);
可以看出compareAndSwapInt(obj, offset, expect, update)中的obj为AtomicInteger类型,
AtomicInteger的value值为volatile类型,在看do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));这里是一个do,while循环,如果obj内的value和expect不相等,
var5 = this.getIntVolatile(var1, var2);一直会
执行,即不断从内存中获取最新的值,来与obj内的value进行比较直到相等为止。从这个字段可以看出复制代码
CAS的缺点
- 只能保证对一个变量的原子性操作
- 长时间自旋会给CPU带来压力
- ABA问题