《Java并发编程实战》笔记-OneValueCache与原子引用技术
/** * NumberRange * <p/> * Number range class that does not sufficiently protect its invariants * * @author Brian Goetz and Tim Peierls */ public class NumberRange { // INVARIANT: lower <= upper private final AtomicInteger lower = new AtomicInteger(0); private final AtomicInteger upper = new AtomicInteger(0); public void setLower(int i) { // Warning -- unsafe check-then-act if (i > upper.get()) throw new IllegalArgumentException("can't set lower to " + i + " > upper"); lower.set(i); } public void setUpper(int i) { // Warning -- unsafe check-then-act if (i < lower.get()) throw new IllegalArgumentException("can't set upper to " + i + " < lower"); upper.set(i); } public boolean isInRange(int i) { return (i >= lower.get() && i <= upper.get()); } }
NumberRange既不能使用指向不可变对象的volatile引用来安全地实现上界和下界,也不能使用原子的整数来保存这两个边界。由于有一个不变性条件限制了两个数值,并且它们无法在同时更新时还维持该不变性条件,因此如果在数值范围类中使用volatile引用或者多个原子整数,那么将出现不安全的“先检查再运行”操作序列。
可以将OneValueCache中的技术与原子引用结合起来,它在更新上界或下界时能避免NumberRange的竞态条件。
/** * CasNumberRange * <p/> * Preserving multivariable invariants using CAS * * @author Brian Goetz and Tim Peierls */ @ThreadSafe public class CasNumberRange { @Immutable private static class IntPair { // INVARIANT: lower <= upper final int lower; final int upper; public IntPair(int lower, int upper) { this.lower = lower; this.upper = upper; } } private final AtomicReference<IntPair> values = new AtomicReference<IntPair>(new IntPair(0, 0)); public int getLower() { return values.get().lower; } public int getUpper() { return values.get().upper; } public void setLower(int i) { while (true) { IntPair oldv = values.get(); if (i > oldv.upper) throw new IllegalArgumentException("Can't set lower to " + i + " > upper"); IntPair newv = new IntPair(i, oldv.upper); if (values.compareAndSet(oldv, newv)) return; } } public void setUpper(int i) { while (true) { IntPair oldv = values.get(); if (i < oldv.lower) throw new IllegalArgumentException("Can't set upper to " + i + " < lower"); IntPair newv = new IntPair(oldv.lower, i); if (values.compareAndSet(oldv, newv)) return; } } }