《JAVA并发编程实战》第一、二章 简介与线程安全

第一章:简介

程序清单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 instance;
    }
}
class ExpensiveObject{}
程序清单2-4 使用AtomicLong类型的变量来统计已处理请求的数量
/**
 * servlet的状态就是计数器count的状态,count是线程安全的,所以servlet是线程安全的.
 * 如果count不是final类型,那么要分析CountingFactorizer的线程安全性将变得复杂。
 * 如果CountingFactorizer将count修改为指向另一个AtomicLong域的引用,
 * 那么必须确保count的更新操作对于所有访问count的线程都是可见的,
 * 并且还要确保count的值上不存在竞态条件。
 * 
 * 这也是尽可能使用final类型域的另一个原因。
 */
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 lastNumber = new AtomicReference();        //上一次请求的 因数
    private final AtomicReference lastFactors = new AtomicReference();    //上一次请求的 因数分解结果
    
    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 ......
      /*如果内置锁不可重入,这里将无法获得Widget上的锁,
      因为这个锁已经被持有,而线程将永远停顿下去,等待一个永远也无法获得的锁。*/
        super.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和 cacheHist 同步更改                            
              ++hits;
              if(i.equals(lastNumber)){//命中缓存
                  ++cacheHist;
                  factors = lastFactors.clone();
                }
          }
          
          if(factors == null){//缓存未命中,与上次请求的数值不一样
              factors = factor(i);//执行因数分解
              //同步缓存竞态条件 : lastNumber  lastFactors
              //lastNumber = lastFactors[0] * lastFactors[1] * ......
              synchronized(this) {
                  lastNumber = i;
                  lastFactors = factors.clone(); 
                  //因为factors在同步代码块儿外定义,不安全,所以clone()
              }
              encodeIntoResponse(res,factors);
          }
      }
}

你可能感兴趣的:(《JAVA并发编程实战》第一、二章 简介与线程安全)