死锁

死锁

为什么会产生死锁?

只有理解了死锁为什么会产生,才能理解死锁产生需要的条件,同样死锁是一种现象,而不是一种机制.

死锁的出现其实是由于同步原语也就是各种同步机制所带来的,不可能做到百分百避免

  • 当有多个线程为了有限的资源竞争时,有的线程就会因为某一时刻没有空闲的资源而陷入等待.而死锁就是指这些线程都陷于等待其他线程释放资源而陷入了永久的等待.

例如:以下案例:

public class LockTest {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2
    public static void main(String[] args) {

        new Thread(() -> {
        synchronized (resource1) {
            System.out.println(Thread.currentThread() + "get " +
                    "resource1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "waiting get " +
                    "resource2");
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get " +
                        "resource2");
            }
        }
            }, "线程 1").start();
        new Thread(() -> {
        synchronized (resource2) {
            System.out.println(Thread.currentThread() + "get " +
                    "resource2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "waiting get " +
                    "resource1");
            synchronized (resource1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "get " +
                        "resource1");
            }
        }
 }, "线程 2").start();
    }
}
  1. 线程1先行获得资源1,线程2同样也获得资源2,这里为了模仿死锁,因此将线程睡眠大概1s,可以保证他两同时拿到各自的资源.

  2. 线程1/线程2想要获得对方的资源,但是由于资源1/资源2被保护了,互斥的缘故所以迟迟拿不到,因此双方会无限制地等下去

    • 因此,输出如下:

      Thread[线程 1,5,main]get resource1
      Thread[线程 2,5,main]get resource2
      Thread[线程 2,5,main]waiting get resource1
      Thread[线程 1,5,main]waiting get resource2
      

由此,我们再来观看死锁这一现象的性质,首先两个线程作为资源的持有者,除非自身放弃资源的拥有者身份,否则其他人绝对不可能获得,其次,资源获得的时候是互斥的,不允许多个线程同时获得,也就是锁的持有者只有一个人,另外,两个线程都会为了获取资源,而等待,他们本身不知道资源互斥的可能,因此等待的情况是前提,而这种双方互相等待的情景就形成了一个类似的环路.

所以,可以总结出来四个特点:

  1. 不可剥夺条件
  2. 互斥性
  3. 持有等待性
  4. 环路继续等待

图解表示就是这样的:

死锁

而死锁的预防也就比较明了了,根据那四个发生的条件:

既然不可剥夺条件,那么就允许被剥夺呗,但这样又会破坏锁的性质,通常来说锁的不可剥夺是一般不会轻易更改的,那么互斥性呢,既然要避免互斥访问,就意味着保护共享的资源呗,如果将线程a和线程b共同执行共享数据的代码提炼出来放在一个线程执行,也就是这种加锁解锁的执行顺序如果改变,即只可需要排队等待获取资源即可.那么持有等待呢?意味着持有的线程要去释放这一竞争资源,换个角度来说,竞争资源大概率会出现饥饿的情况,即很久都没有获取到资源,因此可以减少锁的时间,也就是只在必要的代码上加锁,尽量提前释放锁,另外环路继续等待本质上和死循环类似,因此要尽量避免循环的出现,尤其是有锁的代码区域.


死锁的避免(银行家算法)

死锁的避免设计思想其实就是每次线程去申请资源时都需要向系统提出申请,系统会根据状态,选择是否分配,而系统存在以下两种状态:

安全状态非安全状态,安全状态就是共享资源按照一定的序列去调度,就不会出现死锁的情况,因此被称为安全序列,而这个安全序列往往至少有1个.而如何去确定这个安全序列就是设计之点.

  • 银行家算法:这个算法的提出者可以说是老朋友了,因为数据结构里真的有很多算法(尤其是图的,我每次看都头疼),也就是Dijkstra这个人,读起来就知道他谁了.言归正传,他是通过模拟分配资源后的状态,来判断是否还处于安全的状态,也就是供需关系是否满足.

    因此:

    • 全局可利用资源
    • 每个线程的最大需求量
    • 已分配的资源数量
    • 还需要的资源数量

例如:假设有三个进程(P1、P2、P3)和三种类型的资源(R1、R2、R3),系统可用的资源数量为(10、5、7)。每个进程对资源的最大需求量和已分配资源量如下:

进程 最大需求量 (Max) 已分配资源量 (Allocation) 还需要的资源量 (Need)
P1 (7, 5, 3) (0, 1, 0) (7, 4, 3)
P2 (3, 2, 2) (2, 0, 0) (1, 2, 2)
P3 (9, 0, 2) (3, 0, 2) (6, 0, 0)

此时给P1,则可用资源数会变为(3,1,4),但等到P1完成后,系统可用的资源数 = (10,6,7),也就是原本的资源数+已分配资源量。而此时在分配给P2,还是P3都是可行的,因为在分配时比较需要的资源量是否足够,这是前提,其任务完成后进程也会释放相应的资源量,因此进程完成任务的顺序被称为安全序列,这道题,无论是{P1,P2,P3},{P2,P1,P3},{P3,P2,P1}都是可行的,如果此时可用资源为(5,5,7)则必须先执行P2释放相应的资源才能够执行P3,再执行P1,因此就只有一个安全序列{P2,P3,P1};

posted @ 2023-06-12 14:55  不会上猪的树  阅读(55)  评论(0)    收藏  举报