java并发编程实战笔记-10-11章

第十章主要介绍活跃性危险,也就是安全性和活跃性的权衡.锁不多不少.
安全性: 就是正确性.锁要够多,不然数据并发访问就错了.
活跃性: 就是性能. 锁不能太多,死锁了,或者太卡了.

10.1 死锁

哲学家问题: 循环等待死锁.
数据库服务器如何解决事务死锁:

  1. 检测到等待关系有向图有环;
  2. 选一个牺牲者kill了.
  3. 应用程序自己重试被kill的事务.

10.1.1 锁顺序死锁

避免死锁的手段1:

控制获取锁的顺序.

如果所有线程获取锁(资源)的顺序一样,就不会死锁.

10.1.2 动态的锁顺序死锁

锁顺序很难处理. 比如两个账户转账.
一种方法是使用俩账户的hash码,比较顺序.
当遇到hash码冲突的时候,使用加时赛:

  1. 定义一个新的锁;
  2. 冲突的线程轮流申请这个锁,然后再申请账户锁.

相当于冲突的线程串行执行.

(其实我觉得对于这个问题,账户肯定有唯一id,用id排序就好了.)

10.1.3 协作对象之间的死锁

如果获取多个锁的操作不在唯一的同一个方法里, 问题变得麻烦.
成因:
方法1: 持有锁时调用外部方法,而外部方法请求了另一个锁.
方法2: 同上.

方法1,方法2获取锁的顺序相反时,可能死锁.

解决方法:
收缩synchronized的范围从方法级缩小到块级.

10.1.4 开放调用

开放调用: 调用方法时不需要持有锁.
也就是方法上没有加synchronized,而是在里头某一个块里用了.

10.1.5 资源死锁

资源不够死锁. 如线程资源\数据库连接资源.

  • 解决方案:
    书里没写, 我觉得可以考虑前文说的”选取牺牲者”方法.

10.2 死锁的避免与诊断

  1. 每个线程只获取一个锁. //比较不现实,涉及原子性的实现.
  2. 顺序获取.
  3. 支持定时的锁. // 代替内置锁

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.)

提高可伸缩性方法总结:

  1. 缩小锁的粒度;
  2. 减少锁的持有时间(好像和上一条差不多);
  3. 尽量使用非独占锁.

推荐文章