这本书的内容是什么?
本书提供了各种实用的设计规则,用于帮助开发人员创建安全的和高性能的并发类。
什么类是线程安全的?
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
无状态的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 };
}
}
重入
如果某个线程试图获得一个已经由他自己持有的锁,那么这个请求就会成功。
“重入”意味着获取锁的操作的粒度是“线程”,而不是“调用”。
用锁来保护状态
注意两点:
1 通常,在简单性与性能之间存在着某种互相制约因素。当实现某个同步策略时,一定不要盲目地为了性能而牺牲简单性。
2 当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O操作或者控制台I/O),一定不要持有锁。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。