十二、多线程基础-死锁

2021-07-03 11:07

阅读:637

标签:自己   严格   计算机系   code   区别   string   生命周期   run   表示   

1、死锁含义
    死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。例如,在某一个计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。
2、死锁产生原因
    1)系统资源的竞争:系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。
    2)进程运行推进顺序不合适:进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。
3、产生死锁的四个必要条件
1)互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2)请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
3)不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
4)循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
4、避免死锁的基本思想
    系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何让这四个必要条件不成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
5、死锁避免和死锁预防的区别
    死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格的防止死锁的出现,而死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。
6、形成死锁的程序示例(2种方法)
方法1:通过synchronized实现
/*
一个简单的死锁类:
当 DeadLock 类的对象 flag==1 时(td1),先锁定 o1,睡眠 500 毫秒
而 td1 在睡眠的时候另一个 flag==0 的对象(td2)线程启动,先锁定 o2,睡眠 500 毫秒
td1 睡眠结束后需要锁定 o2 才能继续执行,而此时 o2 已被 td2 锁定;
td2 睡眠结束后需要锁定 o1 才能继续执行,而此时 o1 已被 td1 锁定;
td1、td2 相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
*/

技术分享图片技术分享图片
public class DeadLock implements Runnable {
    public int flag = 1;
    // 静态对象是类的所有对象共享的
    private static Object o1 = new Object(), o2 = new Object();
    public void run() {
        if (flag == 1) {
            System.out.println(Thread.currentThread().getName()+"flag=" + flag);
            synchronized (o1) {
                try {
                    //sleep(100)这样可以防止Thread1在启动时,一下子就获取到了o1和o2锁
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("同时持有o1和o2对象锁");
                }
            }
        }
        if (flag == 0) {
            System.out.println(Thread.currentThread().getName()+"flag=" + flag);
            synchronized (o2) {
                try {
                    //sleep(100)这样可以防止Thread2在启动时,一下子就获取到了o1和o2锁
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("同时持有o2和o1对象锁");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock td1 = new DeadLock();
        DeadLock td2 = new DeadLock();
        td1.flag = 1;
        td2.flag = 0;
        // td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。
        // td2 的 run()可能在 td1 的 run()之前运行
        Thread thread1=new Thread(td1);
        Thread thread2=new Thread(td2);
        thread1.setName("Thread1");
        thread2.setName("Thread2");
        thread1.start();
        thread2.start();
    }
}
View Code

方法2:通过Lock接口实现:首先获得锁的线程1执行完毕后没有释放锁导致线程2无法获取锁。
/*
Lock接口的主要方法:
lock():获取锁,如果锁被暂用则一直等待
unlock():释放锁
tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true
tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间
lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事
 */

技术分享图片技术分享图片
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest1 {
    /*    ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,
    但功能更强大。ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,
    调用 lock() 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回
    */
    private Lock lock = new ReentrantLock();

    // 需要参与同步的方法
    private void method(Thread thread) {
        lock.lock();
        try {
            System.out.println("线程名" + thread.getName() + "获得了锁");
        } catch (Exception e) {
            e.printStackTrace();
        } /*finally {
            lock.unlock();
            System.out.println("线程名" + thread.getName() + "释放了锁");
        }*/
    }

    public static void main(String[] args) {
        final LockTest1 lockTest = new LockTest1();
        // 线程1
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                lockTest.method(Thread.currentThread());
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                lockTest.method(Thread.currentThread());
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}
View Code

7、避免死锁程序示例(2种方法)
1)加锁顺序(线程按照一定的顺序加锁)
2)设置加锁时限-->参看AvoidDeadLock2
7.1、设置加锁顺序:项目功能简介
    flag=1的t1线程 ,执行时先锁定 o1,睡眠 500 毫秒
    在t1线程执行时 flag=2的t2线程也开始执行,执行时先先锁定 o2,睡眠 500 毫秒
    t1睡眠结束后需要锁定 o2 才能继续执行,而此时 o2 已被 td2 锁定;
    t2睡眠结束后需要锁定 o1 才能继续执行,而此时 o1 已被 td1 锁定;t1和t2都相互等待,都需要得到对方锁定的资源才能继续进行,从而死锁。
如何避免死锁方法1(设置设置加锁顺序):应该让t2在开始执行前先等待t1执行完毕再执行 也就是t1.join();这样就可以避免死锁

技术分享图片技术分享图片
public class AvoidDeadLock1 {
    public int flag = 1;
    // 静态对象是类的所有对象共享的
    private static Object o1 = new Object(), o2 = new Object();

    public void money(int flag) {
        this.flag = flag;
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("当前的线程是"
                            + Thread.currentThread().getName() + " "
                            + "flag 的值" + "1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("当前的线程是"
                            + Thread.currentThread().getName() + " "
                            + "flag 的值" + "0");
                }
            }
        }
    }
    /*
    1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。
    2)finally:异常处理语句结构的一部分,表示总是执行。
    3)finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法
    提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法
    被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对
    象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用
    */
    public static void main(String[] args) {
        final AvoidDeadLock1 td1 = new AvoidDeadLock1();
        final AvoidDeadLock1 td2 = new AvoidDeadLock1();
        td1.flag = 1;
        td2.flag = 0;
        // td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。
        // td2 的 run()可能在 td1 的 run()之前运行
        final Thread t1 = new Thread(new Runnable() {
            public void run() {
                td1.flag = 1;
                td1.money(1);
            }
        });
        t1.setName("线程t1");
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                // TODO Auto-generated method stub
                try {
                    // 让 t2 等待 t1 执行完
                    t1.join();// 核心代码,让 t1 执行完后 t2 才会执行
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                td2.flag = 0;
                td2.money(0);
            }
        });
        t2.setName("线程t2");
        t2.start();
    }
}
View Code

7.2、设置加锁时限 (线程尝试获取锁的时候加上一定的时限, 超过时限则放弃对该锁的请求, 并释放自己占有的锁)
/*
 设置加锁时限:项目功能简介
 flag=1的t1线程 ,执行时先锁定 o1,睡眠 500 毫秒
 在t1线程执行时 flag=2的t2线程也开始执行,执行时先先锁定 o2,睡眠 500 毫秒
 t1睡眠结束后需要锁定 o2 才能继续执行,而此时 o2 已被 td2 锁定;
 t2睡眠结束后需要锁定 o1 才能继续执行,而此时 o1 已被 td1 锁定;t1和t2都相互等待,都需要得到对方锁定的资源才能继续进行,从而死锁。
 如何避免死锁方法2(设置加锁时限):应该让t1先获取到锁,如果获取不到就等待5秒,如果5秒后还是获取不到就返回false。获取到锁后就开始执行,
 执行完后释放此锁。t2做同样的处理。
 */

技术分享图片技术分享图片
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class AvoidDeadLock2 {
    public int flag = 1;
    // 静态对象是类的所有对象共享的
    private static Object o1 = new Object(), o2 = new Object();
    public void money(int flag) throws InterruptedException {
        this.flag = flag;
        if (flag == 1) {
            synchronized (o1) {
                Thread.sleep(500);
                synchronized (o2) {
                    System.out.println("当前的线程是"
                            + Thread.currentThread().getName() + " "
                            + "flag 的值" + "1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                Thread.sleep(500);
                synchronized (o1) {
                    System.out.println("当前的线程是"
                            + Thread.currentThread().getName() + " "
                            + "flag 的值" + "0");
                }
            }
        }
    }

    public static void main(String[] args) {
        /*
         * ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized
         * 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 ReentrantLock
         * 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。
         * 如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和
         * getHoldCount()方法来检查此情况是否发生。
         */
        final Lock lock = new ReentrantLock();
        final AvoidDeadLock2 td1 = new AvoidDeadLock2();
        final AvoidDeadLock2 td2 = new AvoidDeadLock2();
        td1.flag = 1;
        td2.flag = 0;
        // td1,td2 都处于可执行状态,但 JVM 线程调度先执行哪个线程是不确定的。
        // td2 的 run()可能在 td1 的 run()之前运行

        final Thread t1 = new Thread(new Runnable() {
            public void run() {
                // TODO Auto-generated method stub
                String tName = Thread.currentThread().getName();

                td1.flag = 1;
                try {
                    // 获取不到锁,就等 5 秒,如果 5 秒后还是获取不到就返回 false
                    if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
                        System.out.println(tName + "获取到锁!");
                    } else {
                        System.out.println(tName + "获取不到锁!");
                        return;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    td1.money(1);
                } catch (Exception e) {
                    System.out.println(tName + "出错了!!!");
                } finally {
                    lock.unlock();
                    System.out.println("当前的线程是"
                            + Thread.currentThread().getName() + "释放锁!!");
                }
            }
        });
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                String tName = Thread.currentThread().getName();
                // TODO Auto-generated method stub
                td1.flag = 1;
                try {
                    // 获取不到锁,就等 5 秒,如果 5 秒后还是获取不到就返回 false
                    if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
                        System.out.println(tName + "获取到锁!");
                    } else {
                        System.out.println(tName + "获取不到锁!");
                        return;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                try {
                    td2.money(0);
                } catch (Exception e) {
                    System.out.println(tName + "出错了!!!");
                } finally {
                    lock.unlock();
                    System.out.println("当前的线程是"
                            + Thread.currentThread().getName() + "释放锁!!");
                }
            }
        });
        t2.start();
    }
}
View Code

 

十二、多线程基础-死锁

标签:自己   严格   计算机系   code   区别   string   生命周期   run   表示   

原文地址:https://www.cnblogs.com/jiarui-zjb/p/9623100.html


评论


亲,登录后才可以留言!