Java并发之Atomic类
作者:碳水怪兽👾 发布于:2021/10/24
Atomic类初见
小张在开发的过程中,遇到一个需求,需要实现一个线程安全的计数器,于是他随手写出了下面的代码
public class Counter {
private int count;
public Counter(int count) {
this.count = count;
}
public synchronized void add(int num){
this.count += num;
}
public synchronized void sub(int num){
this.count -= num;
}
public synchronized void incr(){
add(1);
}
public synchronized void desc(){
sub(1);
}
}
研发主管看到他的代码后,建议他先去了解下Java并发包中的Atomic如何再重写这个功能,小张在简单了解Atom类后,写出了下面的代码
public class AtomCounter {
private volatile AtomicInteger count;
public AtomCounter() {
this.count = new AtomicInteger(0);
}
public void add(int num){
count.getAndAdd(num);
}
public void sub(int num){
count.getAndAdd(-1 * num);
}
public void incr(){
add(1);
}
public void decr(){
sub(-1);
}
}
研发主管为了知道小张是否有弄懂Atomic类的原理,问下他下面的问题,这2种累加器的原理和区别?
小张的回答:
第一种使用了悲观锁,认为会有很多其他线程会操作数据,对数据操作前先加锁,保证每次只有一个线程能操作数据,从而保证线程安全
第二种使用了乐观锁(CAS),认为不会有很多其他线程来操作数据,于是在操作数据前判断数据有没有被其他线程修改(因为使用了volatile保证了可见性),如果没有被其他线程修改,那么可以继续接下来的操作,如果被其他线程修改了,就需要重新读取,然后重复上面的操作。
ABA问题与AtomicStampedReference类
上面提到,CAS是通过值比较实现,如果一个线程先将A修改为B,然后把B修改为A,此时其他的线程就就会判断这个数据没有被其他线程修改过。
为了解决了这个问题,JUC提供了AtomicStampedReference类,该类有一个内部类Pair,Pair类中有2个数据,reference就是我们操作的数据,stamp就是版本号,通过同时比较reference和stamp就可以解决ABA问题
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T var1, int var2) {
this.reference = var1;
this.stamp = var2;
}
static <T> AtomicStampedReference.Pair<T> of(T var0, int var1) {
return new AtomicStampedReference.Pair(var0, var1);
}
}
AtomicStampedReference的compareAndSet函数
public boolean compareAndSet(V var1, V var2, int var3, int var4) {
AtomicStampedReference.Pair var5 = this.pair;
return var1 == var5.reference && var3 == var5.stamp && (var2 == var5.reference && var4 == var5.stamp || this.casPair(var5, AtomicStampedReference.Pair.of(var2, var4)));
}
LongAdder
上面的AtomicInteger在高并发的情况下,会有多个线程同时竞争对一个数据的修改,这种情况下会有线程一直自旋,JUC提供了LongAdder来解决这个问题,将一个变量分为多个变量,多个线程不用竞争一个资源,这样就增加了并发度(有点类似ConcurrentHashMap,减少锁的粒度)。

需要注意的是longAdder不能保证强一致性,因为在累加时,并没有对cell数组加锁,sum函数如下
public long sum() {
Cell[] var1 = this.cells;
long var3 = this.base;
if (var1 != null) {
for(int var5 = 0; var5 < var1.length; ++var5) {
Cell var2;
if ((var2 = var1[var5]) != null) {
var3 += var2.value;
}
}
}
return var3;
}