Java死锁、活锁、饥饿的区别

1. Java死锁

1.1. 死锁定义

死锁:是指两个或两个以上的进程( 或线程) 在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去。

1.2.产生死锁的原因

根本原因:在申请锁时发生了交叉闭环申请。

  1. 是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环
    例如:线程在获得了锁A 并且没有释放的情况下去申请锁B,这时, 另一个线程已经获得了锁B,在释放锁B 之前又要先获得锁A,因此闭环发生,陷入死锁循环。
  2. 默认的锁申请操作是阻塞的

1.3. 死锁的比喻

解释:

线程A或者B需要过独木桥(使用该进程),而C还没有走完(进程还在占用),于是三方僵死;
也可以是没有C 的情况下,A和B互不礼让僵死.
A和B都认为自己优先级较高应该使用该进程.

1.4. 产生死锁的必要条件

1、互斥条件:所谓互斥就是进程在某一时间内独占资源。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:进程已获得资源, 在末使用完之前, 不能强行剥夺。
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。因此可以写下如下的预防死锁的方法。

1.5.处理死锁的思路。

  1. 预防死锁

    破坏死锁的四个必要条件中的一个或多个来预防死锁。

  2. 避免死锁

    和预防死锁的区别就是,在资源动态分配过程中,用某种方式防止系统进入不安全的状态。

  3. 检测死锁

    运行时出现死锁,能及时发现死锁,把程序解脱出来

  4. 解除死锁

    发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程。

所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用其它对象的延时方法和同步方法。

2. Java活锁

2.1. 活锁定义

任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。

2.2. 活锁的比喻

解释:

线程A和B都需要过桥(都需要使用进程),而都礼让不走(那到的系统优先级相同,都认为不是自己优先级高),就这么僵持下去.

比如在一间教室里,A要出去,B要进来,门只能容得下一个人进出,而它们在门口相遇了,所以A往后退了一步意思是B先进,而B也退了一步意思是A先出去;之后A往前走了一步,B也往前走了一步,俩人又都堵在了门口,所以又都同时退一步,然后再同时进一步,同时退一步,同时进一步……

把A和B都比做一个线程的话,这两个线程虽然都没有停止运行,但是却无法向下执行,这种情况就是所谓的活锁。

为了解决这个问题,需要在遇到冲突重试时引入一定的随机性。比如A和B在门口相遇都后退时,A隔一秒后再前进,B隔两秒后再前进,这样就不会有同时走到门口的尴尬了~

2.3. 活锁和死锁的区别

  • 处于活锁的实体是在不断的改变状态,所谓的“ 活”;
  • 处于死锁的实体表现为等待
  • 活锁有可能自行解开,死锁则不能。

3. Java饥饿

3.1. 饥饿定义

一个或者多个线程因为种种原因无法获得所需要的资源, 导致一直无法执行的状态。

3.2. Java 中导致饥饿的原因:

  1. 高优先级线程吞噬所有的低优先级线程的CPU 时间。
  2. 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前
    持续地对该同步块进行访问。
  3. 线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait 方
    法),因为其他线程总是被持续地获得唤醒。

3.3. 饥饿的比喻

解释

这是个独木桥(单进程),桥上只能走一个人,B来到时A在桥上,B等待;
而此时比B年龄小的C来了,B让C现行(A走完后系统把进程分给了C),
C上桥后,D又来了,B又让D现行(C走完后系统把进程分个了D)
以此类推B一直是等待状态.

在“首堵”北京的某一天,天气阴沉,空气中充斥着雾霾和地沟油的味道,某个苦逼的临时工交警正在处理塞车,有两条道A和B上都堵满了车辆,其中A道堵的时间最长,B相对相对堵的时间较短,这时,前面道路已疏通,交警按照最佳分配原则,示意B道上车辆先过,B道路上过了一辆又一辆,A道上排队时间最长的确没法通过,只能等B道上没有车辆通过的时候再等交警发指令让A道依次通过,这也就是ReentrantLock显示锁里提供的不公平锁机制(当然了,ReentrantLock也提供了公平锁的机制,由用户根据具体的使用场景而决定到底使用哪种锁策略),不公平锁能够提高吞吐量但不可避免的会造成某些线程的饥饿。

3.4. 饥饿与死锁、活锁的区别

与死锁不同的是饥饿在以后一段时间内还是能够得到执行的,如那个占用资源的线程结束了并释放了资源。

进程会处于饥饿状态是因为持续地有其它优先级更高的进程请求相同的资源。不像死锁或者活锁,饥饿能够被解开。例如,当其它高优先级的进程都终止时并且没有更高优先级的进程到达。