SynchronizedFactorizer.class
package com.tree.thread;
import java.math.BigInteger;
public class SynchronizedFactorizer implements Servlet {
private BigInteger lastNumber;
private BigInteger[] lastFactors;
public synchronized void service(ServletRequest req,ServletResponse resp){
BigInteger i = extractFromRequest(req);
if(i.equals(lastNumber)){encodeIntoResponse(resp,factors);}
else{
BigInteger[] factors = factor(i);
lastNumber = i;
lastFactors = factors;
encodeIntoResponse(resp,factors);
}
}
}
CachedFactorizer.class
package com.tree.thread;
import java.math.BigInteger;
public class CachedFactorizer implements Servlet {
private BigInteger lastNumber;
private BigInteger[] lastFactors;
private long hits;
private long cacheHits;
public synchronized long getHits(){return hits;}
public synchronized double getCacheHitRatio(){
return (double)cacheHits/(double)hits;
}
public void service(ServletRequest req,ServletResponse resp){
BigInteger i = extractFromRequest(req);
BigInteger[] factors = null;
synchronized (this){
++hits;
if(i.equals(lastNumber)){
++cacheHits;
factors = lastFactors.clone();
}
}
if (factors == null){
factors = factor(i);
synchronized(this){
lastNumber = i;
lastFactors = factors.clone();
}
}
encodeIntoResponse(resp,factors);
}
}
SynchronizedFactorizer中,将service方法声明为synchronized,所以在同一个时间中只有一个线程可以访问service方法—这确保了线程的安全性,但是方式过于极端。
如果同一时间只有一个线程可以访问到,那么进行并发编程又有什么意义呢?就好像是拿到驾照,为了安全,只让你开玩具车;那么拿到驾照又有什么意义呢?
在这种情况下,如果Servlet正在处理一个大数的因数分解,那么在它可以处理一个新的运算开始前,其他用户必须等待,直到当前的请求完成。此运行方式描述为弱并发(poor concurrency):限制并发调用数量的,并非并发可用的处理器资源,而是程序自身的结构。一个可行的策略是:通过缩小synchronized块的范围来维护线程安全性的同时,提供并发的性能。CachedFactorizer重新构造了Servlet。第一个synchronized块保护着检查再运行的操作以检查我们是否可以返回缓存的结果;另一个synchronized缓存了number和factors的同步更新。
决定synchronized块的大小需要权衡各种设计的要求,包括安全性(最重要的!)、简单性和性能。通常简单性和性能之间是相互牵制的(类似于功的黄金定律)。实现一个同步策略时,不要过早地为了性能而牺牲简单性(这是对安全性潜在的威胁)。当使用锁的时候,应该清楚块中的代码的功能,以及它的执行过程中是否会很耗时。无论是作运算密集型的操作,还是执行一个可能存在潜在阻塞的操作,如果线程长时间的占用锁,就会引发活跃度与性能风险的问题。有些耗时的操作,如网络或控制台I/O,难以快速完成。执行这些操作期间不要占用锁。