2019独角兽企业重金招聘Python工程师标准>>>
volatile关键字,在我之前的博客 Volatile - 用途 中已经简单讲解过,当时提出了volatile在多线程中是不安全的。本文将重点介绍一种使用方式,可以实现线程安全。
首先,来看一段不安全的示例:
@NotThreadSafepublic class UnsafeCachingFactorizer implements Servlet {private final AtomicReference<BigInteger> lastNumber = new AtomicReference();private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference();public void service(ServletRequest req, ServletResponse resp){BigInteger i = extractFromRequest(req);if (i.equals(lastNumber.get())){encodeIntoResponse(resp, lastFactors.get());} else {BigInteger[] factors = factor(i);lastNumber.set(i);lastFactors.set(factors);encodeIntoResponse(resp, factors);}}
}
上面的示例中,尽管lastNumber和lastFactors这些原子引用本身是安全的,但是无法保证lastFactors中缓存的因数之积等于在lastNumber中缓存的值。也就是说,lastNumber和lastFactors无法保证同一时间只有一个线程修改、无法同时更新,存在竞态条件。
针对上面的竞态条件,提出了一种修复方式:volatile - 可以考虑创建一个不可变的类来包含这些需要原子方式执行的操作数据。看代码:
@Immutable
class OneValueCache {private final BigInteger lastNumber;private final BigInteger[] lastFactors;public OneValueCache(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);}}
}
@ThreadSafepublic class VolatileCachedFactorizer implements Servlet {private Volatile OneValueCache cache = new OneValueCache(null, null);public void service(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);}
}
对于在访问和更新多个相关变量时出现的竞态条件问题,可以通过将这些变量全部保存在一个不可变对象中来消除。如果是一个可变的对象,那必须使用锁来确保原子性。如果要更新这些变量,那么可以创建一个新的容器对象。
此处,OneValueCache是不可变的,并且在每条相应的代码路径中只会访问一次。通过使用包含多个状态变量的容器对象来维持不变性条件,并且使用一个volatile类型的引用来确保可见性,从而保证了线程安全。