学习一

这本书的内容是什么?

本书提供了各类实用的设计规则,用于帮助开发人员建立安全的和高性能的并发类。

什么类是线程安全的?

当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

无状态的sevlet是线程安全的, 当无状态变为有状态时就是不安全的

@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 };
    }
}

竞态条件(Race Condition)

在并发编程中,因为不恰当的执行时序而出现的不正确结果是一种很是重要的状况,被称之为竞态条件。 
  1)当某个计算结果的正确性取决于多线程的交替执行时序是,那么就会出现竞态条件。换句话说,那就是正确的结果取决于运气。
  2)竞态条件的本质——基于可能失效的观察结果来作出判断或者执行某个计算。
  这类竞态条件被称之为“先检查后执行”。
  下面是一种常见状况,延迟初始化。
    @NotThreadSafe
    public class LazyInitRace {
        private ExpensiveObject instance = null;
    
        public ExpensiveObject getInstance() {
            if (instance == null)
                instance = new ExpensiveObject();
            return instance;
        }
    }
    class ExpensiveObject { }

复合操做

UnsafeCountingFactorizer 和 LazyInitRace  都包含一组须要以原子方式执行(或者说不可分割)的操做。

加锁机制

相似AtomicLong的AtomicRreference来管理因数分解的数值及分解结果? 

 // 这个方法不正确,尽管这些原子引用自己都是现成安全的,可是组合在一块儿就不是线程安全的了。 
 //存在lastNumber和lastFactors没有同时更新的状况
 @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};
        }
    }


要保持状态一致性,就须要在单个原子操做中更新全部先关的状态变量。

内置锁

每一个java对象均可以用作一个实现同步的锁,这些锁被称之为内置锁(Intrinsic lock)或监视器锁(Monitor Lock)。线程在进入同步代码块(Synchronized Block)以前会自动得到锁,而且在退出同步代码块时自动释放锁,而不管是经过正产的控制路径退出,仍是经过从代码块中抛出异常退出。
得到锁的位移方法就是进入由这个锁保护的同步代码快或者方法。
@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 };
    }
}

重入

若是某个线程试图得到一个已经由他本身持有的锁,那么这个请求就会成功。
“重入”意味着获取锁的操做的粒度是“线程”,而不是“调用”。java

用锁来保护状态

注意两点:编程

1 一般,在简单性与性能之间存在着某种互相制约因素。当实现某个同步策略时,必定不要盲目地为了性能而牺牲简单性。
2 当执行时间较长的计算或者可能没法快速完成的操做时(例如,网络I/O操做或者控制台I/O),必定不要持有锁。
相关文章
相关标签/搜索