Java原子类
一、CAS
什么是CAS,CAS就是Compare and Swap
CAS是一种无锁算法
原理:
对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
举个例子,表示一下CAS的原理。假设t1在与t2线程竞争中线程t1能去更新变量的值,而其他线程都失败。(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。t1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值变为了57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。
代码:
public final int getAndAddInt(Object var1, long var2, int var4) {
//1 操作的对象,2 对象中字段的偏移量,3 原来的值,4 要修改的值。
int var5;
do {
var5 = this.getIntVolatile(var1, var2);//得到当前的值。
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
上面的代码是原子类中的一个例子。
ABA问题:
CAS乐观锁机制确实能够提升吞吐,并保证一致性,但在极端情况下可能会出现ABA问题。就是期望值被从A改为B,又从B改为A.CAS还能成功。
解决方法:加版本号。
二、原子类
原子类,顾名思义就是不可分割的,当原子类去执行操作的话是一步合成的。
以前的时候我们并发操作的时候,回去选择加锁。加锁还是有很多影响的。比如:性能损耗、逻辑复杂。所以在JDK1.5的时候Doug Lea写了原子类,也是在JUC包下。
1、原子类介绍
1.1、原子更新基本类型
下面是三种基本原子类型,以及他们常用的API。
AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.getAndAdd(1); //以原子方式将给定值添加到当前值。
atomicInteger.compareAndSet(1,2); //如果当前值==期望值,则以原子方式将该值设置为给定的更新值。
atomicInteger.get(); //获取当前值
atomicInteger.getAndIncrement(); //以原子方式将当前值增加一。
atomicInteger.getAndDecrement(); //以原子方式将当前值减一。
atomicInteger.addAndGet(2); //以原子方式将给定值添加到当前值。
atomicInteger.weakCompareAndSet(1,2); //如果当前值==期望值,则以原子方式将该值设置为给定的更新值。可能会虚假地失败,并且不提供排序保证,因此,很少是compareAndSet的适当替代方法。
AtomicBoolean atomicBoolean = new AtomicBoolean();
atomicBoolean.lazySet(true); //最终设置为给定值。
AtomicLong atomicLong = new AtomicLong(1L);
atomicLong.addAndGet(2L); //以原子方式将给定值添加到当前值。
//基本和Integer相似,就不一一列举了。
总的来说,它们的底层都使用了CAS,我们来找一个看看。
atomicInteger.addAndGet(2);
//第一层
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + 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)); //var4是我们想要增加的值
return var5; //注意这个var5是加var4之前的值。
}
1.2、原子更新数组
我们再来看一下原子更新数组的一些基本操作
AtomicIntegerArray array = new AtomicIntegerArray(16);
array.set(1,16); //将位置i的元素设置为给定值。
array.addAndGet(1,13); //以原子方式将给定值添加到索引i处的元素。
System.out.println(array.get(1)); //获取位置i的当前值。
AtomicLongArray longArray = new AtomicLongArray(16);
// 基本和AtomicIntegerArray的API相似
longArray.set(1,2);
我们看一下AtomicIntegerArray
的内部原理
public AtomicIntegerArray(int length) { //我们看一下初始化方法,内部就是维护了一个private final int[] array;
array = new int[length];
}
看一下array.addAndGet(1,13);
这个方法的内部原理。
//第一层
public final int addAndGet(int i, int delta) {
return getAndAdd(i, delta) + delta;
}
//第二层
public final int getAndAdd(int i, int delta) {
return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
private long checkedByteOffset(int i) {
if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
return byteOffset(i);
}
//第三层
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;
}
//还是调用的这个方法。
AtomicReferenceArray
引用数组的基本使用方法。
AtomicReferenceArray referenceArray = new AtomicReferenceArray(16);
referenceArray.set(1, "222");
//将给定函数应用于当前值和给定值,以原子方式更新索引i处的元素,并返回更新后的值。
// 该功能应无副作用,因为当尝试更新由于线程之间的争用而失败时,可以重新应用该功能。
// 应用该函数时,将索引i处的当前值作为其第一个参数,并将给定的update作为第二个参数。
referenceArray.accumulateAndGet(1, "999", (x, y) -> x.toString() + y);
//输出结果为222999
我们看一下这个方法内部原理。
public final E accumulateAndGet(int i, E x,
BinaryOperator<E> accumulatorFunction) {
long offset = checkedByteOffset(i); //获取下标在数组中的偏移量
E prev, next;
do {
prev = getRaw(offset); //获取当前的值
next = accumulatorFunction.apply(prev, x); //获取函数执行后的值
} while (!compareAndSetRaw(offset, prev, next)); //CAS赋值。
return next;
}
1.3、原子更新属性、引用。
原子的更新某个类里面的字段时,需要使用原子更新字段类。
我们来看看有一个原子类型。
AtomicStampedReference、AtomicReference、FiledUpdater。
不过使用上述几个引用,有几个规则必须遵守
- 字段必须是voliatile类型的,在线程之间共享变量时能保持可见性。
- 字段的描述类型是与调用者的操作对象字段保持一致。
- 也就是说调用者可以直接操作对象字段,那么就可以反射进行原子操作。
- 对父类的字段,子类不能直接操作的,尽管子类可以访问父类的字段。
- 只能是实例变量,不能是类变量,也就是说不能加static关键字。
- 只能是可修改变量,不能使用final修饰变量,final的语义,不可更改。
- 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)
AtomicLongFieldUpdater
这个只对long进行更新的使用方法。
AtomicLongFieldUpdater<Son> updater = AtomicLongFieldUpdater.newUpdater(Son.class, "id");
Son son = new Son();
son.setId(9);
updater.addAndGet(son,7);
如果我们想更改包装类,就使用AtomicReferenceFieldUpdater
AtomicReferenceFieldUpdater<Son,String> fieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Son.class,String.class,"name");
fieldUpdater.compareAndSet(son,"777","666");
AtomicStampedReference
前面我们提到了ABA问题并且提到了解决办法就是加版本号,这个原子类就是带版本号的。
AtomicStampedReference<String> stampedReference = new AtomicStampedReference("aaa", 1);
stampedReference.compareAndSet("aaa", "bbb", stampedReference.getStamp(), stampedReference.getStamp() + 1);
小结
我们去看原子类内部时,发现最终都是使用了CAS的方法。原子类就是在CAS发展来的。