一.原子性
无状态对象一定是线程安全的,假设我们希望增加一个“命中计数器”来统计所处理的请求数量,一种直观的方法是增加一个long类型的域。
@NotThreadSafe public class UnsafeCountingFactorizer extends GenericServlet implements Servlet { private long count = 0; public long getCount() { return count; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); ++count; encodeIntoResponse(resp, factors); } void encodeIntoResponse(ServletResponse res, BigInteger[] factors) { } BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } BigInteger[] factor(BigInteger i) { // Doesn't really factor return new BigInteger[] { i }; } }
UnsafeCountingFactorizer 并非线程安全的,因为递增操作++count并非原子的。它包含三个独立的操作:读取count的值,将值加1,然后将计算结果写入count。
1.竞态条件
UnsafeCountingFactorizer 中存在多个竞态条件。当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。最常见的竞态条件类型就是“先检查后执行”操作。
2.复合操作
@ThreadSafe public class CountingFactorizer extends GenericServlet implements Servlet { private final AtomicLong count = new AtomicLong(0); public long getCount() { return count.get(); } public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); count.incrementAndGet(); encodeIntoResponse(resp, factors); } void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {} BigInteger extractFromRequest(ServletRequest req) {return null; } BigInteger[] factor(BigInteger i) { return null; } }
通过用AtoimcLong来代替long类型的计数器,能够确保所有对计数器状态的访问操作都是原子的。
二.加锁机制
假设我们希望提升Servlet的性能:将最近的计算结果缓存起来,当两个连续的请求对相同的数值进行因数分解时,可以直接使用上一次的计算结果,而无需重新计算。
@NotThreadSafe public class UnsafeCachingFactorizer extends GenericServlet implements Servlet { private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>(); private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>(); 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); } } void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { } BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } BigInteger[] factor(BigInteger i) { // Doesn't really factor return new BigInteger[]{i}; } }
这种方法并不正确。尽管这些原子引用本身都是线程安全的,但在UnsafeCachingFactorizer 中存在着竞态条件,这可能产生错误的结果。尽管对set方法每次调用都是原子的,但仍然无法同时更新lastNumber和lastFactores。在线程A获取这两个值的过程中,线程B可能修改了它们。
1.内置锁
Java提供了一种内置的锁机制来支持原子性:同步代码块。
@ThreadSafe public class SynchronizedFactorizer extends GenericServlet implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; public synchronized void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber)) encodeIntoResponse(resp, lastFactors); else { BigInteger[] factors = factor(i); lastNumber = i; lastFactors = factors; encodeIntoResponse(resp, factors); } } void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { } BigInteger extractFromRequest(ServletRequest req) { return new BigInteger("7"); } BigInteger[] factor(BigInteger i) { // Doesn't really factor return new BigInteger[] { i }; } }
2.重入
三.用锁来保护状态