概述
AtomicInteger底层同步CAS就是一种乐观锁思想的应用,采用CAS算法实现原子性运算。
并发和并行的区别:
- 并发 通过CPU的调度,切换时间片,就算只有一个核心,也可以实现“同时做很多事情”
- 并行 同一时刻,执行不同的任务,重点是同一时刻
方法
- incrementAndGet() 原子性的加1,并返回加1后的值,先加再get
- getAndIncrement() 原子性的加1,返回的是加1之前的值,这两个方法类似i++和++i
- decrementAndGet() ,getAndDecrement() 原子性减1,返回方式同上
- getAndAdd(int delta) ,addAndGet(int delta) 原子性加delta,返回方式同上
- compareAndSet(int expect, int update) 如果传入的expect等于atomicInteger现有的值,就将atomicInteger改为update
源码解析
基于1.8,incrementAndGet()方法调用的是unsafe的getAndAddInt(),返回值是getAndAddInt+1,刚方法每次从内存中读取数据然后将此数据和 +1 后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止,而native方法 compareAndSwapInt是借助C来调用CPU底层指令实现的(多核lock)
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
关于unsafe的类可以如下测试:
private volatile int value;
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
valueOffset = unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("value"));
System.out.println(unsafe.getAndAddInt(this, valueOffset, 1));
System.out.println(unsafe.getAndAddInt(this, valueOffset, 1));
输出 0 1
unsafe.getAndAddInt() ,这个方法会比较当前值是否相等,然后设置为newValue
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;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
示例
int num =0;
AtomicInteger atomicInteger = new AtomicInteger();
ExecutorService service = BasicThreadFactory.getExecutorService();
CountDownLatch countDownLatch = new CountDownLatch(10000);
for(int i=0;i<10000;i++){
service.submit(()->{
num++;
atomicInteger.incrementAndGet();
countDownLatch.countDown();
});
}
countDownLatch.await();
service.shutdown();
System.out.println("res = " + num);
System.out.println("atomic = " + atomicInteger.get());
输出结果,atomic =10000,res不总是等于10000
如何避免ABA
如果value的值先由A变成B,再由B变成A,虽然结果是正确的,但是过程是有问题的。
一个解决方法:使用atomicStampedReference 不仅判断值是否相等,还要判断版本号,AtomicStampedReference 还可以存储string,这样的话,可以将多个变量拼接存储
String str = "abc";
AtomicStampedReference atomicStampedReference = new AtomicStampedReference(str,1);
atomicStampedReference.compareAndSet(str, "abcd", 1, 2);
缺点
- 如果CAS不成功,会一直占用cpu资源