死锁
经典的“哲学家进餐”问题就很好的描述了死锁的状况;
死锁的成因:如果每个人都拥有其他人需要的资源,但是同时又等待其他人已经拥有的资源,并且每个人在获取所有需要的资源之前都不会放弃已经拥有的资源;
一、锁顺序死锁
1.左右锁问题
- 发生死锁的原因:两个线程试图以不同的顺序来获得相同的锁。
- 解决办法:如果按照相同的顺序请求锁,那么就不会出现循环的加锁依赖性,因此就不会产生死锁。如果每个需要锁L和锁M的线程都以相同的顺序获取L和M锁,那么就不会发生死锁了;
2.动态锁顺序
- 发生死锁的原因:当获取锁的顺序取决于参数的传入顺序时,那么就没法保证死锁不会发生。
- 解决办法:必须定义锁的顺序,并在整个应用程序中按照这个顺序来获取锁;
例如:可以使用System.identityHashCode来比较参数的hash值的大小,以此来作为获取锁的顺序;
二、在协作对象之间发生的死锁
- 发生死锁的原因:在持有锁的情况下调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁,或者阻塞时间过长,导致其他线程无法及时获取当前被持有的锁;
- 解决方法:采用开发式调用,即在调用外部方法时,不持有锁;
三、资源死锁
- 发生死锁的原因:有界线程池/资源池与相互依赖的任务一起使用;
例如:一个任务提交另外一个任务,并且等待被提交任务在单线程的Executor中执行完成。这种情况下,第一个任务就会永远等待下去,并使得另外一个任务以及在这个Executor中执行的所有其他任务都停止执行。 - 解决办法:通过使用无界线程池;
活锁
- 概念:线程将不断重复执行相同的操作,而且总是会失败。该问题尽管不会阻塞线程,但是也不能继续执行下去。
例如:在处理事务消息的应用中:如果不能成功的处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列的开头。如果消息处理器在处理某种特定类型的消息时存在错误并导致它失败,那么每当这个消息从队列中取出并传递到存在错误的处理器时,都会发生事务回滚。 - 解决方法:在并发应用中,通过等待随机长度时间和回退可以有效的避免活锁的发生;