Android 常见面试题——死锁
死锁的定义
一组相互竞争系统资源或进行通信的进程间的“永久”阻塞。如两个线程相互等待对方释放同步监视器时就会发生死锁。
一旦发生死锁,整个程序不会发生任何异常,不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
死锁的原因
(1)竞争不可抢占性资源
(2)竞争可消耗资源
当系统中供多个进程共享的资源如打印机,公用队列等,其数目不足以满足诸进程的需要时,会引起诸进程对资源的竞争而产生死锁。
(3)进程推进顺序不当
进程在运行过程中,请求和释放资源的顺序不当,也同样会导致产生进程死锁。
如何避免死锁
1、在系统中不允许进程在已获得某种资源的情况下,申请其他资源。即要想出一个办法,阻止进程在持有资源的同时申请其他资源。
要求每个进程提出新的资源申请前,释放它所占有的资源。这样,一个进程在需要资源时,须先把它先前占有的资源R释放掉,然后才能提出对S的申请,即使它可能很快又要用到资源R。
2、如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。
3、如果一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另一个进程,要求它释放资源。只有在任意两个进程的优先级都不相同的条件下,该方法才能预防死锁。
4、将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。这样做就能保证系统不出现死锁。
典型的死锁情形
线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了。
所以,写程序时应该尽量避免同时获得多个锁。如果一定有必要这么做,则要按先后顺序获得锁。
线程的运行、阻塞和死亡
线程运行
如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,如果机器只有一个CPU,那么在任何时刻都只有一个线程处于运行状态。当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的现象。
线程阻塞
当一个线程运行后,它不可能一直处于运行状态(除非它执行体足够短,瞬间完成),线程在运行过程中需要被中断,目的是使其他的线程获得执行的机会。
1、线程调用sleep()方法主动放弃所占的处理器资源
2、线程试图调用了一个阻塞式IO方法,在该方法返回之前,该线程阻塞
3、线程试图获得一个同步监视器,但是该同步监视器正被其他线程持有
4、线程在等某个通知(notify)
5、程序调用了线程的suspend()方法将该线程挂起。单证方法容易导致死锁,所以尽量避免使用。
被阻塞的线程会在合适的时候重新进入就绪状态,注意是重新进入就绪状态,不是运行状态。即被阻塞的线程阻塞解除后,必须重新等待线程调度器再次调度它。
解除阻塞
1、调用sleep()方法的线程经过了指定时间
2、线程调用的阻塞式IO方法已经返回
3、线程成功地获取了试图获取的同步监视器
4、线程正在等待某个通知时,其他线程发出了一个通知
5、处于挂起的线程被调用了resume()恢复方法
线程死亡
线程会以如下三种方式结束,结束后就处于死亡状态:
1、run()或call()方法执行完成
2、线程抛出一个未捕获的Exception或者Error
3、直接调用线程的stop()方法结束,但该方法容易导致死锁,不推荐
注:如果要测试某个线程是否已经死亡,可以调用该线程的isAlive()方法,当处于就绪、运行、阻塞三种状态时,返回true;当处于新建、死亡状态,返回false。