使用Volatile类型来发布不可变对象
尝试用两个AtomicReferences变量来保存最新的数值及其因数分解结果,但这种方式并非是线程安全的,因为我们无法以原子方式来同时读取或者更新这两个相关的值。同样,用volatile类型的变量来保存这些值也不是线程安全的。然而,在某些情况下,不可变对象提供一种弱形式的原子性。
因式分解Servlet将执行两个原子操作:更新缓存的结果,以及通过判断缓存中的数值是否等于请求的数值来决定是否直接读取缓存中的因数分解结果。每当需要对一组相关数据以原子方式来执行某个操作时,就可以考虑创建一个不可变的类来包含这些数据。
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
public class OnValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;
public OnvalueCache(BigInteger i, BigInteger[] factors) {
lastNumber i;
lastFactors = Arrays.copyOf(factors, factors.length);
}
public BigInteger[] getFactors(BigInteger i) {
if(lastNumber == null || !lastNumber.equals(i)){
return null;
}else {
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}
}
对于在访问和更新多个相关变量时出现的竞争条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。如果一个可变的对象,那么久必须使用锁来确保原子性。
程序VolaileCachedFacorizer使用了OneValueCache 来保存缓存的数值以及因数。当一个线程将volatile类型的cache设置为引用一个新的OneValueCache时,其他线程就会立即看到新缓存的数据。
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.math.BigInteger;
public class VolatileCacheFactorizer implements Servlet {
private volatile OneValueCache cache = new OneValueCache(null, null);
public void services(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if(factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors);
}
encodeIntoResponse(resp, factors);
}
}
通过使用包含多个状态变量的容器对象来维持不变性条件,并使用一个volatile类型的引用来确保可见性,使得Volatile CachedFactorizer没有显示地使用锁的情况下仍然是安全的。