在讲问题前,我先说明一下什么是活跃度?
一个并发应用及时执行的能力称作活跃度。
我主要讲死锁问题,顺带介绍一下饥饿,弱响应性和活锁。
死锁
死锁这个词大家都听过,我先来罗列一下产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
那程序里产生死锁的原因主要是:
(1)因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
下面我再说一下在Java中常见的几种出现死锁的情况:
1.锁顺序死锁:
这个很简单,那就是两个线程通过不同的顺序请求多个相同的锁时发生的死锁。
举个例子:就是T1线程拥有L1锁,T2线程拥有L2锁,然后T1线程要L2锁,T2线程要L1锁,这时就发生死锁了。
2.动态的所顺序死锁:
有时候获得锁的顺序并不是固定的,比如说有些程序是根据传入的参数,来确定锁的顺序的。
这个时候我们就得人为地控制顺序了,比如把哈希值大的当作第一个锁。
3.协作对象间的死锁:
有时候我们并没有显示地加锁,但调用别的类的方法时,该方法加了锁。所以我们编程时讲究开放调用,当调用的方法不需要持有锁时,我们称之为开放调用。其实这个问题主要提醒我们一点,那就是尽量缩小同步块的范围,不要随便就在方法上加个synchronized关键字。
4.资源死锁:
如资源连接池可能出现的分配与请求问题,线程饥饿死锁等。
那我们如何避免和诊断死锁呢?
首先尽量让每个线程一次至多获得一个锁。当然有时候业务需要,做不到这样,那我们就得控制锁的顺序来避免死锁。尽量进行开放调用。
当然除此以外,我们可以尝试使用定时的锁,如tryLock()方法等。当超时时,先放弃资源,并给出提示或抛出异常,当然也可以将给任务重新放进队列。
我们也可以通过线程转储来分析死锁。
饥饿
当线程访问它所需要的资源时却被永久拒绝,以至于不能再继续进行,这样就发生了饥饿。比如使用线程的优先级不当就可能造成饥饿。
弱响应性
如GUI线程执行耗时操作时,就会造成弱响应性问题。我们可以把耗时任务分载到后台线程来避免该问题。
活锁
活锁是线程中活跃度失败的另一种形式,尽管没有阻塞,线程却仍然不能继续,因为它不断重试相同的操作,却总是失败。
这类似于两个人在一个走廊中过路:Alphonse移到左边让Gaston通过,Gaston移到右边让Alphonse通过。由于他们被彼此阻塞,Alphonse移到右边,Gaston移到左边。他们又会发生阻塞,一直循环下去。。。 我个人理解,这和死循环差不多。