使用AtomicInteger
、AtomicBoolean
等原子操作类可以完成原子操作。它的各种操作都是基于Unsafe
类的,你可以看到函数的画风都是下面这样:
// AtomicIneger.java
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
但Unsafe
的实现是native方法,无法查看源码:
// Unsafe.java
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
参考Unsafe类 Unsafe涉及底层硬件级别的原子操作,其函数大多是native的,如下:
// Unsafe.java
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
程序员不能直接调用它,只有授信的代码才能获得该类的实例,当然JDK库里面的类是可以随意使用的。 我们只需要知道,它是原子操作的底层实现即可。
参考张龙netty教程的P.81~P.84 先说结论:volatile + AtomicIntegerFieldUpdater的实现方案相比AtomicInteger能节省更多内存空间。
AtomicInteger
类实现了CAS,但如果频繁使用其实例,会占用不少内存:64位机器上,一个AtomicInteger
对象地址占用8个字节,其内部维护的int值value
占用4个字节。也就是说,在每个用到CAS操作的整形值的地方,都需要维护一个AtomicInteger
对象,也就是占用4+8=12字节。volatile
+ AtomicIntegerFieldUpdater
的方法实现,就能减少存储。你只需要维护一个静态变量AtomicIntegerFieldUpdater
,并为需要用到CAS操作的int值声明为volatile
即可。只需要先分配一个静态变量,然后在每个用到CAS操作的整形值的地方,都只需要维护一个volatile int
变量,也就是占用4字节。在netty中,需要频繁创建/销毁大量的ByteBuf对象,用于存储信息。为了及时销毁ByteBuf对象,需要使用引用计数。netty构建了AbstractReferenceCountedByteBuf
,实现了引用计数的功能。
在程序运行的过程中,会创建大量的AbstractReferenceCountedByteBuf
实例,每个需要在其内部维护一个整形值,代表引用计数。实例可能会被不同对象同时引用,当它不被引用时,应当及时释放空间。为了让引用计数在多线程下有序地增减,引用计数应当用CAS实现。
那么我们要使用AtomicInteger
实现CAS吗?
AbstractReferenceCountedByteBuf
实例里的引用计数用一个AtomicInteger
变量实现,确实简单。但这样每个实例都要为它分配4+8=12字节的空间。volatile int
+ AtomicIntegerFieldUpdater
,只需先为静态变量AtomicIntegerFieldUpdater
分配空间,然后每个实例只需要占用4字节即可。能节约很多内存。
正因为如此,netty使用了AtomicIntegerFieldUpdater
来实现引用计数的CAS。我们看下源码:
// AbstractReferenceCountedByteBuf.java
public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
private static final long REFCNT_FIELD_OFFSET;
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
private volatile int refCnt = 1;
...
@Override
public ByteBuf retain(int increment) {
return retain0(checkPositive(increment, "increment"));
}
private ByteBuf retain0(final int increment) {
int oldRef = refCntUpdater.getAndAdd(this, increment);
...
return this;
}
...
}
AbstractReferenceCountedByteBuf
通过retain和release来增加、释放引用。而这两个函数是基于refCntUpdater
对refCnt
的CAS操作。
这样,每个AbstractReferenceCountedByteBuf
实例只需要为refCnt
分配4字节空间即可,相比于AtomicInteger
的12字节,节省了很多空间。
参考什么是CAS问题
AtomicInteger、AtomicReference等类会带来ABA问题。 ABA问题,就是要维护的变量被替换后,又设置回来。类实例将无法辨别它被替换过。 举个例子,假设有一个变量x:
问题是:线程1在查询x的过程中,x的值已经经历了A->B->A的转变,而线程1对此无所知。这就是ABA问题了。
我们看到compareAndSet的源码:
// AtomicReference.java
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
根据注释,它的比较是基于地址数值比较的,而与"equals"方法没有关系。stackoverflow上的问题 也印证了这一点。 也就是说,AtomicReference的ABA问题指的是"其引用可能会改变",而不是指"引用的变量的值可能会改变"。
AtomicStampedReference