我们知道,实现了ReferenceCounted接口的类的对象都会在引用计数的作用下进行显式的回收。当引用计数为0时,这个对象就不能再被访问了。而这个接口提供了两个方法给我们来操作引用计数。
- retain()
- release()
而这个操作是必须保证是在多线程的情况下是安全的,所以他们的操作都是原子的。以retain为例
private ReferenceCounted retain0(int increment) {
int oldRef = refCntUpdater.getAndAdd(this, increment);//使用更新器增加引用计数值,并返回旧值
if (oldRef <= 0 || oldRef + increment < oldRef) {//如果旧值为0,则恢复这个对象的引用计数值并抛出异常
// Ensure we don't resurrect (which means the refCnt was 0) and also that we encountered an overflow.
refCntUpdater.getAndAdd(this, -increment);
throw new IllegalReferenceCountException(oldRef, increment);
}
return this;
}
所以关键就在这个refCntUpdater上。我们看一下他的声明
private static final AtomicIntegerFieldUpdater<AbstractReferenceCounted> refCntUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCounted.class, "refCnt");
他是一个静态,所以是被这个类所有的对象共享的。这个对象是AtomicIntegerFieldUpdater类通过静态方法创建的。我们看一下这个类。
public abstract class AtomicIntegerFieldUpdater<T>
{
@CallerSensitive//这个方法有两个参数,一个是需要进行原子操作类,另一个是需要进行原子操作的这个类的字段的名称
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> paramClass, String paramString)
{
return new AtomicIntegerFieldUpdaterImpl(paramClass, paramString, Reflection.getCallerClass());
}
我们查看一下在retain0方法中调用的原子操作方法getAndAdd方法
public int getAndAdd(T paramT, int paramInt)
{
int i;
int j;
do
{
i = get(paramT);
j = i + paramInt;
} while (!compareAndSet(paramT, i, j));
return i;
}
其中采用的方法为自询锁的方法,反复进行操作,如果操作失败,则再次循环,直至成功更新。
compareAndSet方法的实现是一种native方法,不作讨论。他的作用就是再在多线程的环境下,如果两个线程同时操作了这个对象,并且同时进入到了这个方法当中,那么就对比这个对象中更新器指定的字段的旧值跟实际上这个对象该值(因为可能会发生在获取这个属性值之后,另一个线程对其进行了修改),如果判定这两个值相等,则表示没有线程在这一瞬间对这个字段进行了更新,则compareAndSet更新成功。如果值判定不相等,则表示有线程在这一瞬间执行了更新操作,需要再次回到循环中进行。简称cas操作。
而AtomicIntegerFieldUpdater的cas操作对需要进行原子操作的属性是有要求的,
- 必须保证这个属性是int类型,而不是Integer类型,如果是Integer,则使用AtomicReferenceFieldUpdater。
- 必须保证这个属性是被volatile修饰符修饰(如果不是,那么一个线程对其的修改不会立刻更新到内存当中,所以对其他线程不可见),volatile的作用就是保证了其在多线程环境下的可见性。
- 必须保证这个属性不是static.
- 必须保证这个属性是可以在外部被访问到的,因为AtomicIntegerFieldUpdater肯定需要通过反射的方式获取指定的属性的值。