十二、多线程基础-死锁

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();
    }
}

方法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();
    }
}

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();
    }
}

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();
    }
}

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

时间: 2024-10-10 06:13:14

十二、多线程基础-死锁的相关文章

Tensorflow深度学习之十二:基础图像处理之二

Tensorflow深度学习之十二:基础图像处理之二 from:https://blog.csdn.net/davincil/article/details/76598474 首先放出原始图像: 1.图像的翻转 import tensorflow as tf import cv2 # 这里定义一个tensorflow读取的图片格式转换为opencv读取的图片格式的函数 # 请注意: # 在tensorflow中,一个像素点的颜色顺序是R,G,B. # 在opencv中,一个像素点的颜色顺序是B,

十、多线程基础-需要强化的知识点

1.sleep()和wait()方法异同 sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器1)Thread.sleep():方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态.在调用sleep()方法的过程中,线程不会释放对象锁.2)Object.wait():线程会放弃对象锁,进入等待此对象的等待锁定

python 学习笔记十二 CSS基础(进阶篇)

1.CSS 简介 CSS 指层叠样式表 (Cascading Style Sheets) 样式定义如何显示 HTML 元素 样式通常存储在样式表中 把样式添加到 HTML 4.0 中,是为了解决内容与表现分离的问题 外部样式表可以极大提高工作效率 外部样式表通常存储在 CSS 文件中 多个样式定义可层叠为一 css存在方式: 元素内联 页面嵌入 外部引入 语法:style = 'key1:value1;key2:value2;' 1.元素内联(在标签中使用css) <!--在标签使用--> &

“全栈2019”Java多线程第十九章:死锁详解

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第十九章:死锁详解 下一章 "全栈2019"Java多线程第二十章:同步方法产生死锁的例子 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Java学习小组&q

嵌入式 Linux进程间通信(十二)——多线程同步

嵌入式 Linux进程间通信(十二)--多线程同步 多线程编程中有三种线程同步机制:互斥锁.信号量.条件量.本文将使用生产者消费者问题编程实践三种线程同步方式. 生产者.消费者问题:生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费.消费者线程从缓冲区中获得物品,然后释放缓冲区.当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区.当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来. 一.互斥锁

C#编程总结(二)多线程基础

C#编程总结(二)多线程基础 无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作.要使应用程序能够快速响应用户操作,同时在用户事件之间或者甚至在用户事件期间利用处理器,最强大的方式之一是使用多线程技术. 多线程:线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.如果某个线程进行一次长延迟操作, 处理器就切换到另一个线程执行.这样,多个线程的并行(并发)执行隐藏了

JavaSE基础笔记十二

第十一章 多线程 理解程序.进程.线程的概念 程序可以理解为静态的代码. 进程可以理解为执行中的程序. 线程可以理解为进程的近一步细分,程序的一条执行路径. 2.如何创建java程序的进程(重点) 方式一:继承于Thread类 ①创建一个继承于Thread的子类 ②重写Thread类的run()方法,方法内实现此子线程要完成的功能 ③创建一个子类的对象 ④调用线程的start():启动此线程,调用相应的run()方法 ⑤一个线程只能够执行一次start 方式二:实现Runnable接口 ①创建一

“全栈2019”Java多线程第四十二章:获取线程与读写锁的保持数

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第四十二章:获取线程与读写锁的保持数 下一章 "全栈2019"Java多线程第四十三章:查询是否有线程在等待读写锁 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复&quo

JavaScript基础--小案例:在网页指定位置弹出错误信息(十二)

案例分析:点击按钮后,在网页上指定区域,提示错误信息!5秒后,错误信息提示自动消失! 1 <script languag="javascript" type="text/javascript"> 2 var clearId; 3 function test(){ 4 document.getElementById("showMsg").style.cssText="width:200px;height:50px;left:6