《java并发编程实战》笔记(第10章)
来源:互联网 发布:勒夏特列原理 知乎 编辑:程序博客网 时间:2024/06/09 15:41
死锁
经典的“哲学家进餐”问题: 5个哲学家去吃中餐,坐在一张圆桌旁,他们有五根筷子(不是五双),并且每两个人中间放一根筷子。哲学家们时而思考,时而进餐。每个人都需要一双筷子才能吃到东西,并且在吃完后将筷子放回原处继续思考。
如果每个人都立即抓住自己左边的筷子,然后等待自己右边的筷子空出来,但同时又不放下已经拿到的筷子。——产生死锁
也就是 每个人都拥有其他人需要的资源,同时又等待其他人已经拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞。
在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远地等待下去。这种情况就是最简单的死锁形式(或者称为“抱死【Deadly Embrace 】”)
如果所有线程以固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁问题。
//容易发生死锁public class LeftRightDeadlock { private final Object left = new Object() ; private final Object right = new Object() ; public void leftRight(){ synchronized (left) { synchronized (right) { //doSomething(); } } } public void rightLeft(){ synchronized (right) { synchronized (left) { //doSomethingElse(); } } }}
* 动态的锁顺序死锁 考虑下面看似无害的代码 ,将资金从一个账户转到另一个账户,转账前,先获取两个Account对象的锁。
public void transferMoney(Account fromAccount , Account toAccount , int account) throws InsufficientResourcesException{ synchronized (fromAccount) { synchronized (toAccount) { if(fromAccount.getBalance()-account<0){ throw new InsufficientResourcesException() ; }else{ fromAccount.debit(account); toAccount.credit(account) ; } } } }
所有的线程看似都按照相同的顺序来获得锁,但事实上锁的顺序取决于传递给transferMoney的参数顺序,而这些参数顺序取决于外部输入。如果两个线程同时调用transferMoney ,其中一个从X向Y转账,一个从Y向X转账,那么就会发生死锁。
private static final Object tieLock = new Object(); public void transferMoney(final Account fromAccount , final Account toAccount , final int account) throws InsufficientResourcesException{ class Helper{ public void transfer() throws InsufficientResourcesException{ if(fromAccount.getBalance()-account<0){ throw new InsufficientResourcesException() ; }else{ fromAccount.debit(account); toAccount.credit(account) ; } } } /** * 1.hashCode()方法是Object类下面的一个方法,供继承类重写,根据对象内存地址计算哈希值. 2.String类重写了hashCode方法,并改为根据字符序列来计算哈希值. 3.identityHashCode()方法是System类中的静态方法,根据对象内存地址来计算哈希值. */ int fromHash = System.identityHashCode(fromAccount); //System.identityHashCode返回Object.hashCode()的值 int toHash = System.identityHashCode(toAccount); if(fromHash < toHash){ synchronized (fromAccount) { synchronized (toAccount) { new Helper().transfer(); } } } else if(fromHash > toHash){ synchronized (toAccount) { synchronized (fromAccount) { new Helper().transfer(); } } }else{ synchronized (tieLock) { synchronized (fromAccount) { synchronized (toAccount) { new Helper().transfer(); } } } } }
通过System.identityHashCode 可以很好的解决问题(System.identityHashCode中出现散列冲突的频率非常低,如果经常出现散列冲突的情况,那么这个解决方案可能会成为并发性的瓶颈)
在协作对象之间发生的死锁
如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁(可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。开放调用
如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用(Open Call)
通过尽可能地使用开放调用,将更易于找出那些需要获取多个锁的代码路径,因此也就更容易确保采用一致的顺序来获得锁。
在程序中应尽量使用开放调用。与那些在持有锁时调用外部方法的程序相比,更容易对依赖于开放调用的程序进行死锁分析。死锁的避免与诊断
在使用细粒度锁的程序中, 可以通过使用一种两阶段策略来检查代码中的死锁:首先,找出在什么地方将获取多个锁(使这个集合尽量小),然后对所有这些实例进行全局分析,从而确保它们在整个程序中获取锁的顺序都保持一致。尽可能地使用开放调用,这能极大地简化分析过程。
1. 支持定时的锁。 显式使用Lock类中的定时tryLock功能来代替内置锁机制。(参见第13章)
使用内置锁时,只要没有获得锁,就会永远等待下去,而显式锁则可以指定一个超时时限Timeout,在等待超过该时间后tryLock会返回一个失败信息。
2.通过线程转储信息来分析死锁
JVM通过线程转储(Thread Dump)来帮忙识别死锁的发生。 (可使用一些工具 如eclipse的MemoryAnalyzer来分析DUMP文件)其他活跃性危险
1.饥饿
当线程由于无法访问它所需要的资源而不能继续执行时,就会发生饥饿“Starvation”。
引发饥饿的最常见资源就是CPU时钟周期。(百度百科:CPU时钟周期通常为节拍脉冲或T周期,既主频的倒数,它是处理操作的最基本的单位。)
通常,我们尽量不要改变线程的优先级。只要改变了线程的优先级,程序的行为就将与平台相关,并且会导致发生饥饿问题的风险。你经常能发现某个程序会在一些奇怪的地方调用Thread.sleep或Thread.yield,这是因为该程序试图客服优先级调整问题或响应性问题,并试图让低优先级的线程执行更多的时间。
要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。
2.糟糕的响应性
不良的锁管理也可能导致糟糕的响应性。例如:某个线程长时间占用一个锁(比如对一个大容器进行迭代,并对每个元素进行计算密集的处理),那么其他想要访问这个容器的线程就必须等待很长时间
3.活锁
活锁通常发生在处理事务消息的应用程序中:如果不能成功的处理某个消息,那么消息处理机制将回滚整个事务,并将它重新排到队列的开头。由于这条消息每次都被放回到队列开头,因此处理器将被反复调用,并返回相同的结果。这种这种形式的活锁通常是由过度的错误恢复代码造成的。
当多个相互协作的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。就像
要解决这种活锁问题,需要在重试机制中引入随机性。
- 《java并发编程实战》笔记(第10章)
- 《java并发编程实战》笔记(第3章)
- 《java并发编程实战》笔记(第4章)
- 《java并发编程实战》笔记(第6章)
- 《java并发编程实战》笔记(第7章)
- 《java并发编程实战》笔记(第8章)
- 《java并发编程实战》笔记(第9章)
- java并发编程实战第7章
- java并发编程实战第8章
- Java并发编程实战~笔记~章二
- Java并发编程实战~笔记~章三
- Java并发编程实战~笔记~章四
- Java并发编程实战~笔记~章五
- 《java并发编程实战》笔记(1)
- Java并发编程实战笔记(一)
- Java并发编程实战笔记(二)
- java并发编程实战(笔记)
- 《Java并发编程实战》笔记
- spring注解识别一个接口的多个实现类方法
- 方向梯度直方图(HOG)
- Linux
- 病毒、木马、蠕虫与恶意代码关键点
- TCP定时器讲解与TCP三次握手四次挥手
- 《java并发编程实战》笔记(第10章)
- KMP--找子串
- EffectiveC++学习笔记-条款32|33
- 结构算法 002 单链表的头插法和尾插法
- webView之post请求携带请求头及打印日志等剖析
- 前端经典面试题1
- python 字典
- 我的C程序设计语言学习日记#03
- 一维数组、二维数组、字符串总结