产生死锁的四个必要条件

本文详细阐述了产生死锁的四个必要条件:互斥使用、不可抢占、请求保持和循环等待,并通过Java代码示例解释了这些概念,包括如何通过synchronized关键字实现互斥和不可抢占,以及死锁如何在循环等待条件下形成。
摘要由CSDN通过智能技术生成

产生死锁的四个必要条件

  1. 互斥使用: 一个资源每次只能被一个线程使用。这意味着如果一个线程已经获取了某个资源(比如锁),那么其他线程就必须等待,直到该线程释放资源。

  2. 不可抢占: 已经获得资源的线程在释放资源之前,不能被其他线程抢占。只有拥有资源的线程自己能够释放资源,其他线程无法将其强行抢占。

  3. 请求保持: 一个线程在持有至少一个资源的情况下,又请求获取其他资源。这样的情况下,如果其他资源被其他线程持有并且不释放,就会导致请求线程等待,从而可能形成死锁。

  4. 循环等待: 存在一组等待进程 {P1, P2, ..., Pn},其中P1等待P2持有的资源,P2等待P3持有的资源,...,Pn等待P1持有的资源。这样的循环等待条件是死锁的充分条件

注意 以上是必要条件 在数学中 分必要条件 充要条件等 必要条件是四个缺一不可的 

也就是说 要产生死锁 这些条件是不可或缺的

以上四个必要条件分别用java代码解释说明

目录

互斥使用

不可抢占

请求保持

循环等待


互斥使用

互斥是指在多任务处理中,对共享资源的访问进行限制,确保同一时刻只有一个任务(或线程)能够访问共享资源。这种限制保证了对共享资源的安全访问,避免了数据竞争和数据不一致的问题。

在并发编程中,互斥通常通过锁(如Java中的`synchronized`关键字或`Lock`接口)来实现。当一个任务需要访问共享资源时,它会尝试获取锁,如果锁已被其他任务持有,则该任务会被阻塞,直到锁被释放。一旦任务获取到锁,它就可以安全地访问共享资源,在完成操作后释放锁,以便其他任务可以继续访问。

函数起名根据: 

public static void criticalSection1() {
            System.out.println(Thread.currentThread().getName() + "进入临界区");
            System.out.println(Thread.currentThread().getName() + "离开临界区");
    }

    public static void main(String[] args) {
        new Thread(()->{
            criticalSection1();
        }).start();
        new Thread(()->{
            criticalSection1();
        }).start();
    }

对于这段代码 会有这样一个执行结果 因为是并发执行的

可以看到 在进程1进入临界区的时候 0也能进入临界区

接下来我们加上一段锁

private static final Object lock = new Object();

    public static void criticalSection() {
        synchronized(lock) {
            System.out.println(Thread.currentThread().getName() + "进入临界区");
            // 这里是临界区,只有一个线程可以执行这段代码
            System.out.println(Thread.currentThread().getName() + "离开临界区");
        }
    }

    public static void main(String[] args) {
        new Thread(()->{
            criticalSection();
        }).start();
        new Thread(()->{
                    criticalSection();
                }).start();
    }

对于这段代码  只有这一一种执行结果 因为每次只有一个线程能够进入临界区执行代码,确保了临界区内的操作不会被并发执行,从而避免了数据竞争和数据不一致的问题。 表现了互斥等到(两个或多个线程同时想要获取一个资源 但是只能等到另一个释放)

不可抢占

public class Main3 {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 线程1获取锁并执行耗时操作
        new Thread(() -> {
            try {
                // 等待2启动,保证2在1之后获取锁
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(lock) {
                System.out.println("线程1获得了锁");
                try {
                    // 模拟耗时操作
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1释放了锁");
            }
        }).start();

        // 线程2尝试获取锁
        new Thread(() -> {
            try {
                // 等待一段时间,模拟线程2稍晚启动
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(lock) {
                // 线程2无法获取锁,因为锁已被线程1持有
                System.out.println("线程2获得了锁");
            }
            System.out.println("线程2释放了锁");
        }).start();
    }
}

以上代码有两种执行结果

线程2获得了锁
线程2释放了锁
线程1获得了锁
线程1释放了锁
线程1获得了锁
线程1释放了锁
线程2获得了锁
线程2释放了锁

而没有 1获得了锁下一句是2释放了锁 这种情况

这就表现不可抢占的特性,即已经获得资源的线程在释放资源之前,不能被其他线程抢占。

请求保持

先想一下这段话:  一个线程在持有至少一个资源的情况下,又请求获取其他资源。这样的情况下,如果其他资源被其他线程持有并且不释放,就会导致请求线程等待,从而可能形成死锁。

我们可以理解为 

t1线程 在持有lock资源的情况下,又请求获取lock2资源。这样的情况下,如果lock2资源被t2线程持有并且不释放,就会导致请求线程等待,从而可能形成死锁。

public class Main4 {
    private static Object lock = new Object();
    private static Object lock2 = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(()-> {
            synchronized (lock) {
                System.out.println("t1获得lock");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (lock2) {
                    System.out.println("t1获得lock2");
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (lock2) {
                System.out.println("t2获得lock2");
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t1.start();
        t2.start();
    }
}

可以观察到 t2不释放lock2 t1就不能拿到lock2

以上表现出 请求保持是死锁的一个必要条件之一,指的是一个线程在持有至少一个资源的情况下,又请求获取其他资源,但这些资源已被其他线程持有并且不释放,从而导致请求线程等待,可能形成死锁。

循环等待

也就是哲学家进餐问题

public class Main5 {
    private static final Object resource1 = new Object();
    private static final Object resource2 = new Object();

    public static void main(String[] args) {
        // 线程1持有资源1,请求资源2
        Thread t1 = new Thread(() -> {
            synchronized (resource1) {
                System.out.println("线程1持有资源1");
                try {
                    Thread.sleep(1000); // 为了确保线程2先持有资源2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource2) {
                    System.out.println("线程1持有资源2");
                }
            }
        });

        // 线程2持有资源2,请求资源1
        Thread t2 = new Thread(() -> {
            synchronized (resource2) {
                System.out.println("线程2持有资源2");
                synchronized (resource1) {
                    System.out.println("线程2持有资源1");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

导致了循环等待 也就是 存在一组等待进程 {P1, P2},其中P1等待P2持有的资源,P2等待P1持有的资源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值