格言:在程序猿界混出点名堂!
![](https://img.haomeiwen.com/i18759109/f022812d61509771.png)
已经连载了2个周,本书的第2章的解读也要结束了,下周可以开始第3章的解读,说实话,虽然很累,但是每天保持能够坚持读一个章节,慢慢寻味和体会,然后进行组织与总结,把书中最好的东西分享给大家。也是一种快乐。带给快乐的不仅是自己还有你们。另外需要看前面几期的解读,可以添加文章最下面的微信公众号。
《JAVA并发编程实战》解读
【连载】第2章-2.5活跃性与性能
回顾:上一节主要介绍了用锁来构造一种协议来保护共享状态的访问,也介绍了将对象的可变状态封装在对象的内部。这一节还是聊聊同步与活跃性和性能问题的一种微妙关系。
缓存未必带来性能提升
标题里面我们提到了缓存未必带来性能
,既然这么说答案是值得深思的,前面章节的将上一次因数分解的计算结果缓存,以便下次可以直接读取缓存,提升性能,但为了保证缓存的lastNumber和lastFactors的原子操作,Servlet的方法上增加synchronized关键字,试想客户端必须串行来执行请求。这其实也违背了Servlet设计的初衷。这种并发称之为不良并发
。
![](https://img.haomeiwen.com/i18759109/da2b8709d24e227c.jpg)
其实不难看出,是我们锁的粒度太粗,导致性能出现的问题。
减小锁粒度
@ThreadSafe
public class CacheFactorizer implements Servlet{
@GuardedBy("this") private BigInteger lastNumber;
@GuardedBy("this") private BigInteger lastFactors;
@GuardedBy("this") private long hits;
@GuardedBy("this") private long cacheHits;
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;
}
}
}
}
以上代码有几个看点:
- 不难看出,以上程序减小的锁的粒度,将耗时比较长的factor(i),放在在锁的外边。
- 为什么hits、cacheHits计数器不使用原子性的AtomicLong来替换呢?使用多种同步器不仅造成混乱,而且对性能提升也带来不了什么优势。
- 为什么是 factors = lastFactors.clone(); 而不是 factors = lastFactors;?其实这里涉及到一个概念
方法逃逸
,即lastFactors从方法中逃逸出去,方法以外也能对该变量做修改,导致lastFactors不可控。
合理的同步代码块大小
要判断合理代码块的大小,就需要在安全性、简单性、性能之间平衡。
- 安全性:即所见即所知,程序表现出的行为和状态期望一致。说白一点就是逻辑没毛病。这一点必须要保证,不能舍弃。
- 简单性:往往满足简单性,牺牲的往往是性能。比如我们在方法上增加synchronized关键字,逻辑清晰简单。
- 性能:在安全性保证的前提下,减小锁的粒度,用复杂性换取了性能。
因此,在设计同步代码时,要在简单性和性能做出平衡选择。
耗时长的代码块不要使用锁
- 造成线程都在等待,锁的等待时间太长,有可能导致事务未结束数据库连接池的链接占用不释放、微服务下的雪崩等。
- 还有可能造成死锁,之前提到的银行转账死锁的例子,如果同步代码的时间过长,大大提升了死锁的概率。
因此,耗时长的代码块,会带来性能和活跃性问题。
喜欢连载可关注
简书
或者微信公众号
:
简书专题:Java并发编程实战-可爱猪猪解读
https://www.jianshu.com/c/ac717321a386
微信公众号:可爱猪猪聊程序
。
![]()
网友评论