像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问题