【17】死锁与如何避免死锁

(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~

【17】死锁与如何避免死锁

1.死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

1.1产生死锁的三个必要条件

(1)多个操作者(M >=2) 争夺多个资源(N >=2),N <= M(资源数也要小于等于操作者数).
(2)争夺资源的顺序不一致;
(3)拿到资源不放手。

1.2学术化描述死锁

(1)需要满足互斥条件
(2)请求保持(已经拿到了一个资源,但是要拿到一个新的资源,而且我请求的资源被别的线程拿到了)
(3)不剥夺(线程所拿到的资源数不能够被剥夺)
(4)环路等待(一个操作者拿到了A等B,另一个操作者拿到了B等A)。

1.3死锁案例代码

package com.gdc.deadlock.lock;

public class DeadLockDemo {
    //锁对象
    private static Object no13 = new Object();
    private static Object no14 = new Object();

    //拿锁方法1
    private static void zsDo() throws InterruptedException {
        String threadName = Thread.currentThread().getName();

        synchronized (no13){
            System.out.println(String.format("%s get 13 lock",threadName));
            Thread.sleep(100);

            synchronized (no14){
                System.out.println(String.format("%s get 14 lock",threadName));
            }
        }
    }

    private static void lsDo() throws InterruptedException {
        String threadName = Thread.currentThread().getName();

        synchronized (no14){
            System.out.println(String.format("%s get 14 lock",threadName));
            Thread.sleep(100);

            synchronized (no13){
                System.out.println(String.format("%s get 13 lock",threadName));
            }
        }
    }

    private static class PersonThread extends Thread{
        private String name;

        public PersonThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            Thread.currentThread().setName(name);
            //调用方法进入同步块
            try {
                zsDo();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread.currentThread().setName("MainThread");
        PersonThread personThread = new PersonThread("personThread");
        personThread.start();
        lsDo();
    }

}

(1)主线程先拿锁对象13,再拿锁对象14.
(2)PersonThread先拿锁对象14,再拿锁对象13.
(3)以上zsDo与lsDo两个方法分别被主线程与PersonThread线程调用。
(4)在这种情况下就一定会产生死锁,因为如果不产生死锁,肯定会正常打印并执行完成,而实际运行时是两个线程卡住了。
(5)死锁不抛出异常,看不到任何信息的日志, 程序也没有死,但是线程却偏偏不做事情。
(6)程序发生了死锁,只能重新启动程序,用户只能将程序关闭,重新启动,这样还是个很严重的问题。

1.4如何避免死锁

打破以下两个条件的任意一个即可:
(1)争夺资源的顺序不对(争夺资源顺序保持一致);

1.4.1争夺资源顺序保持一致解决死锁
/**
 * 通过让多个线程争夺资源的顺序保持一致解决死锁
 */
public class ResolveDeadLockDemo1 {
    //锁对象
    private static Object no13 = new Object();
    private static Object no14 = new Object();

    //拿锁方法1
    private static void zsDo() throws InterruptedException {
        String threadName = Thread.currentThread().getName();

        synchronized (no13){
            System.out.println(String.format("%s get 13 lock",threadName));
            Thread.sleep(100);

            synchronized (no14){
                System.out.println(String.format("%s get 14 lock",threadName));
            }
        }
    }

    private static void lsDo() throws InterruptedException {
        String threadName = Thread.currentThread().getName();

        synchronized (no13){
            System.out.println(String.format("%s get 13 lock",threadName));
            Thread.sleep(100);

            synchronized (no14){
                System.out.println(String.format("%s get 14 lock",threadName));
            }
        }
    }

    private static class PersonThread extends Thread{
        private String name;

        public PersonThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            Thread.currentThread().setName(name);
            //调用方法进入同步块
            try {
                zsDo();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread.currentThread().setName("MainThread");
        PersonThread personThread = new PersonThread("personThread");
        personThread.start();
        lsDo();
    }

}

(2)拿到资源不放手(拿到资源放手,可以使用Lock提供和API,可以用不同的方式拿锁,例如采用尝试拿锁的方式,即trylock)。

使拿锁的过程放手,使用Lock接口,使用尝试拿锁机制。

1.4.2Lock提供的API
/**
 * 通过尝试拿锁解决死锁问题
 * 场景:
 * 1.主线程中先尝试拿No13 锁,再尝试拿No14锁,No14锁没拿到,连同No13 锁一起释放掉
 * 2.子线程中先尝试拿No14锁,再尝试拿No13锁,No13锁没拿到,连同No14锁一起释放掉
 */
public class TryLock {
    /**
     * 第1个锁
     */
    private static Lock No13 = new ReentrantLock();
    /**
     * 第2个锁
     */
    private static Lock No14 = new ReentrantLock();

    private static class PersonThread extends Thread{
        /**
         * 线程名
         */
        private String name;

        public PersonThread(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            Thread.currentThread().setName(name);
            try {
                secondToFirst();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 先尝试拿No13 锁,再尝试拿No14锁,No14锁没拿到,连同No13 锁一起释放掉
     * @throws InterruptedException
     */
    private static void firstToSecond() throws InterruptedException{
        String threadName = Thread.currentThread().getName();
        Random r = new Random();

        while (true){
            if(No13.tryLock()){
                try {
                    System.out.println(String.format("%s get 13 lock",threadName));
                    if(No14.tryLock()){
                        try {
                            System.out.println(String.format("%s get 14 lock",threadName));
                            System.out.println("firstToSecond do work------");
                            break;
                        }finally {
                            No14.unlock();
                        }
                    }
                }finally {
                    No13.unlock();
                }
            }
            Thread.sleep(r.nextInt(3));
        }
    }

    /**
     * 先尝试拿No14锁,再尝试拿No13锁,No13锁没拿到,连同No14锁一起释放掉
     * @throws InterruptedException
     */
    private static void secondToFirst() throws InterruptedException{
        String threadName = Thread.currentThread().getName();
        Random r = new Random();

        while (true){
            //1.1.先尝试拿No14锁
            if(No14.tryLock()){
                System.out.println(String.format("%s get 14 lock",threadName));
                //1.2.再尝试拿No13锁
                try{
                    if(No13.tryLock()){
                        try {
                            System.out.println(String.format("%s get 13 lock",threadName));
                            System.out.println("secondToFirst do work------");
                            break;
                        }finally {
                            //1.3释放No13锁
                            No13.unlock();
                        }
                    }
                }finally {
                    //1.4释放No14锁
                    No14.unlock();
                }
            }
            //进入等待状态
            Thread.sleep(r.nextInt(3));
        }
    }

    public static void main(String[] args) {
        Thread.currentThread().setName("MainThread");
        PersonThread personThread = new PersonThread("personThread");
        personThread.start();
        try {
            firstToSecond();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

(1)在同一时刻只会有一个线程能够同时成功拿到两把锁
(2)为什么要休眠一小段时间

	Thread.sleep(r.nextInt(3));

(1)如果不休眠一小段时间,两个线程拿锁的时间就会变长,这在并发编程之中称为活锁。
(2)休眠一小段时间是为了让两个线程运行的时间错开,让线程有机会拿到CPU时间片获得执行。

1.4.3官方说法

(1)打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
(2)打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
(3)打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
(4)打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

1.5避免死锁常见的算法

避免死锁常见的算法有有序资源分配法、银行家算法。

1.6活锁

多个线程一直在拿锁释放锁的过程中,一直进行下去,这种情况就称为活锁。

1)线程A B,锁1,22)线程A1<2>首先拿到锁1,尝试着去拿锁23)线程B2<2>首先拿到锁2,尝试着去拿锁14)线程A(1) <2> 接下来将自己持有的锁释放掉,下一次循环里面,再一次拿到了锁1,尝试着去拿锁2.5)线程B(2) <1>尝试着去拿锁1,在不断的拿锁释放锁的过程中,把这种情况称之为活锁。
(6)设置休眠时间Thread.sleep(r.nextInt(3));

2.线程饥饿

老是拿不到CPU的时间片,得不到执行。需要将线程运行的时间错开一点点,让线程有机会获取CPU时间片,从而进入运行状态。

3.打赏鼓励

感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!

3.1微信打赏

在这里插入图片描述

3.2支付宝打赏

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值