第一章:简介
程序清单1-1非线程安全的数值序列生成器
import net.jcip.annotations.NotThreadSafe; @NotThreadSafe public class UnsafeSequence { private int value; /*返回一个唯一的数值*/ public int getValue() { return value++; //三个操作:读取,加一,赋值。 多线程并发操作value可能导致步骤被打乱 } }
程序清单1-2 线程安全的数值序列生成器
public class Sequence { private int value; /*返回一个唯一的数值*/ public synchronized int getValue() { //各个线程串行访问 return value++; } }
第二章:线程安全性
程序清单2-1 一个无状态的Servlet ,各个线程间没有共享状态
@ThreadSafe public class StatelessFactorizer implements Servlet{ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { BigInteger i = extractFromRequest(req); BigInteger[] factories = factor(i); encodeIntoResponse(res,factories); } }
程序清单2-3 延迟初始化中的竞态条件(不要这么做)
@NotThreadSafe public class LazyInitRace { private ExpensiveObject instance = null; //竞态条件 public ExpensiveObject getInstance() { if(instance == null) instance = new ExpensiveObject(); return new ExpensiveObject(); } } class ExpensiveObject{}
程序清单2-4 使用AtomicLong类型的变量来统计已处理请求的数量
/*servlet的状态就是计数器count的状态,count是线程安全的,所以servlet是线程安全的*/ public class CountingFactorizer implements Servlet { private final AtomicLong count = new AtomicLong(0); //原子变量类,实现在数值和对象引用上的原子状态转换 public long getCount() { return count.get(); } public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { BigInteger i = extractFromReqest(req); BigInteger[] factors = factor(i); count.incrementAndGet(); encodeIntoResponse(res,factors); } }
程序清单2-5 该Servlet在没有足够原子性保证的情况下对其最近计算结果进行缓存(不要这么做)
/** * 因数分解: * 在数学中,因数分解,又称素因数分解,是把一个正整数写成几个约数的乘积。例如,给出45这个数,它可以分解成3×3×5,根据算术基本定理,这样的分解结果应该是独一无二的 * * * 希望提升Servlet性能,将最近的计算结果缓存起来,当2个连续的请求对相同的数值进行因数分解时,可以直接使用上一次的计算结果. * * 原子引用本身是线程安全的,但是业务逻辑中存在竞态条件 : * lastFactors[0] * lastFactors[1] * ..... = lastNumber; 此条件不被破坏,Servlet才是线程安全的。 */ @NotThreadSafe public class UnsafeCachingFactorizer implements Servlet { //lastNumber、lastFactors本身是线程安全的 private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>(); //上一次请求的 因数 private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>(); //上一次请求的 因数分解结果 public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { BigInteger i = extractFromReqest(req); if(i.equals(lastNumber)){ encodeIntoResponse(res,lastFactors.get()); } else { BigInteger[] factors = factor(i); lastNumber.set(i); //无法保证同时与lastFactors更新, lastFactors.set(factors); //无法保证同时与lastNumber更新 encodeIntoResponse(res,factors); } } }
程序清单2-6 该Servlet能正确地缓存最新的最近计算结果,但并发性确非常糟糕(不要这么做)
@ThreadSafe public class SynchronizedFactorizer implements Servlet { @GuardedBy("this") private BigInteger lastNumber; //上一次请求的 因数 @GuardedBy("this") private BigInteger[] lastFactors; //上一次请求的 因数分解结果 public synchronized void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { BigInteger i = extractFromReqest(req); if(i.equals(lastNumber)){ encodeIntoResponse(res,lastFactors); } else { BigInteger[] factors = factor(i); lastNumber = i; lastFactors = factors; encodeIntoResponse(res,factors); } } }
程序清单2-7,如果内置所不可重置,那么以下代码将发生死锁
/** * 一线程请求其它线程持有的锁时,发出请求的线程将会被阻塞。 * 然而,内置锁是可重入的,某线程试图获得自己持有的锁时,这个请求将成功。 * “重入” 意味着获取锁的粒度是“线程”,而不是“调用” * * “重入”的实现:计数器。锁一次 +1,释放一次-1。 =0时锁被释放。 * @author guchunchao * */ public class Widget { public synchronized void widget() { //TODO ...... } } class LoggingWidget extends Widget { @Override public synchronized void widget() { // TODO ...... super.widget(); //如果内置锁不可重入,这里将无法获得Widget上的锁,因为这个锁已经被持有,而线程将永远停顿下去,等待一个永远也无法获得的锁。 } }
复合操作的原子性无法通过单个方法的synchronized保证
//Vector类的每一个方法都是synchronized,仅保证单个方法的原子性;当多个方法组合在一起的复合操作时,不足以保证复合操作的原子性,需要额外加锁 if(!vector.contains(name)) vector.add(name);
程序清单2-8 缓存最近执行因数分解的数值及其计算结果的Servlet
//注意:在单个变量上实现原子操作来说,原子变量(Atomic.....)是很有用的,不要把原子变量和同步代码块同时使用,这会带来混乱,也不会再性能或安全性上带来任何好处。
@ThreadSafe public class SynchronizedFactorizer { @GuardedBy("this") private BigInteger lastNumber; //上一次请求的 因数 @GuardedBy("this") private BigInteger[] lastFactors; //上一次请求的 因数分解结果 @GuardedBy("this") private long hits; //请求数量 @GuardedBy("this") private long cacheHist; //缓存命中数量 /**获取访Servlet调用问数量*/ public synchronized long getHits() {return this.hits;} /**获取缓存命中率*/ public synchronized double getHitsRatio() {return (double)cacheHist / (double)hits;} /**更细粒度的同步*/ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { BigInteger i = extractFromReqest(req); //从页面获取待因数分解的数 BigInteger[] factors = null; synchronized(this) { ++hits; //同步竞态条件:请求数 和 缓存命中数 可能同步更改 if(i.equals(lastNumber)){ //命中缓存 ++cacheHist; //同步竞态条件:请求数 和 缓存命中数 可能同步更改 factors = lastFactors.clone(); } } if(factors == null){ //缓存未命中,与上次请求的数值不一样 factors = factor(i); //执行因数分解 synchronized(this) { lastNumber = i; //同步缓存竞态条件 lastNumber = lastFactors[0] * lastFactors[1] * ...... lastFactors = factors.clone(); //同步缓存竞态条件,用clone是因为factors是在同步代码块儿外定义的变量 } encodeIntoResponse(res,factors); } } }