第十章主要介绍活跃性危险,也就是安全性和活跃性的权衡.锁不多不少.
安全性: 就是正确性.锁要够多,不然数据并发访问就错了.
活跃性: 就是性能. 锁不能太多,死锁了,或者太卡了.
10.1 死锁
哲学家问题: 循环等待死锁.
数据库服务器如何解决事务死锁:
- 检测到等待关系有向图有环;
- 选一个牺牲者kill了.
- 应用程序自己重试被kill的事务.
10.1.1 锁顺序死锁
避免死锁的手段1:
控制获取锁的顺序.
如果所有线程获取锁(资源)的顺序一样,就不会死锁.
10.1.2 动态的锁顺序死锁
锁顺序很难处理. 比如两个账户转账.
一种方法是使用俩账户的hash码,比较顺序.
当遇到hash码冲突的时候,使用加时赛:
- 定义一个新的锁;
- 冲突的线程轮流申请这个锁,然后再申请账户锁.
相当于冲突的线程串行执行.
(其实我觉得对于这个问题,账户肯定有唯一id,用id排序就好了.)
10.1.3 协作对象之间的死锁
如果获取多个锁的操作不在唯一的同一个方法里, 问题变得麻烦.
成因:
方法1: 持有锁时调用外部方法,而外部方法请求了另一个锁.
方法2: 同上.
方法1,方法2获取锁的顺序相反时,可能死锁.
解决方法:
收缩synchronized
的范围从方法级缩小到块级.
10.1.4 开放调用
开放调用: 调用方法时不需要持有锁.
也就是方法上没有加synchronized
,而是在里头某一个块里用了.
10.1.5 资源死锁
资源不够死锁. 如线程资源\数据库连接资源.
- 解决方案:
书里没写, 我觉得可以考虑前文说的”选取牺牲者”方法.
10.2 死锁的避免与诊断
- 每个线程只获取一个锁. //比较不现实,涉及原子性的实现.
- 顺序获取.
- 支持定时的锁. // 代替内置锁
10.2.2 死锁的诊断
通过Thread Dump信息.
方法: 向JVM发送SIGQUIT信号.
命令行:
1 | kill -3 [id] |
jdk1.5: 有内置锁信息, 无显式锁信息;
jdk1.6: 有内置锁信息, 少量显式锁信息.
(?那岂不是意味着尽量不要使用显式锁?)
10.3 其他活跃性危险
活跃性危险: 太卡
包括: 死锁,饥饿,丢失信号,活锁.
10.3.1 饥饿
如短作业优先调度的时候,长作业就会饥饿.
10.3.2 糟糕的响应性
GUI线程优先级太低, 响应性就会差.
10.3.3 活锁
重复的失败.
如线程不停得获取锁,释放锁,似乎能完成,但其实总也完成不了,无间地狱,死循环.
- 案例1: 华龙道
发数据包碰撞. 重试算法一样,总是在相同的路口再碰撞.
解决方法:
增加随机性. 重试时间加一个随机参数.
- 案例2:
过度的错误恢复.
解决方法:
不过度.
第十一章 性能与可伸缩性
线程的使用
目的: 提高性能.
缺陷: 提高复杂性.
场景: 多cpu系统,任务不是cpu密集的.
衡量手段: 看cpu使用情况图.
11.1.1 可伸缩性
可伸缩性: 当增加资源时(cpu,内存等等),程序吞吐量/处理能力增加比例适中.
可伸缩性与性能往往矛盾.
单线程的性能优化方法 往往导致=> 可伸缩性下降.
11.2 Amdahl定律
增加计算资源时,程序理论上能实现的最高加速比.
Speedup <= 1/ ( F + (1-F)/N )
N: N个处理器
F: 必须串行的任务比例.
N趋近于无穷大时,加速比为 1/F.
(F为50%时,加速比最大为2.)
提高可伸缩性方法总结:
- 缩小锁的粒度;
- 减少锁的持有时间(好像和上一条差不多);
- 尽量使用非独占锁.