CAS和ABA

CAS和ABA

前面一篇关于Java的文章当中谈到了关于Java的Atomic变量的使用,其没有使用传统的(悲观)锁来实现,而是使用了乐观锁,compare and set的方法来实现。上次在分析的时候的时候没有注意到其中一个很大的问题ABA问题,其可能会造成严重的错误。

前面说到CAS的原理设计在对数据进行更新的时候把原值和内存值比较以来判断其有没有被修改过。但是这其中可能存在的问题就是:内存的原值是A,其他线程把其值改为了B,然后又将其改回了A,这时CAS会认为值没有被修改过而进行更新操作,但是实际上已经被修改过了,这种情况下可能会有比较严重的问题。

AtomicStampedReference类

AtomicInteger等类对ABA问题并没有防御,Atomic包中的AtomicStampedReference类是实现了ABA问题防御的泛型类,当需要防御ABA问题时,应该使用它而并非简单的原子变量。

其防御ABA问题的思路是出了记录变量的值,还记录下其一个时间戳:

    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }

在其实现当中使用了静态内部类来描述,如果对元素的更新依赖于原来的值,我们需要同时取出reference和stamp,在更新值的时候把原来的值和时间戳都传入:

    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

只有当现在的时间戳和取出运算时候的时间戳相等的时候,才会进行更新,只有在使用时保证时间戳不会ABA,(比如保证时间戳递增)就能解决ABA问题。