多线程之死锁,哲学家就餐问题的实现

news2024/11/27 22:40:51

1.死锁是什么

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

2.哲学家就餐问题

有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。

 

为了进一步阐述死锁的形成, 很多资料上也会谈论到 "哲学家就餐问题".

解决办法一,哲学家要进餐时,要么同时拿起两支筷子,要么一支筷子都不拿.
package thread4;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//解决办法一,哲学家要进餐时,要么同时拿起两支筷子,要么一支筷子都不拿.
class Philosopher {
    //哲学家编号
    public int id;

    public Philosopher(int id) {
        this.id = id;
    }

    //思考
    public void thinking() throws InterruptedException {
        System.out.println("我是哲学家" + this.id + "号,我正在思考!");
        Thread.sleep(1000);
    }

    //吃饭
    public void eating() throws InterruptedException {
        System.out.println("我是哲学家" + this.id + "号,我正在吃饭!");
        Thread.sleep(1000);
    }

    //拿筷子
    public void takeUp(Chopsticks chopsticksLeft, Chopsticks chopsticksRight) throws InterruptedException {
        synchronized (Test3.locker) {
            if (chopsticksLeft.used || chopsticksRight.used) {
                Test3.locker.wait();
            }
        }
        chopsticksLeft.used = true;
        chopsticksRight.used = true;
        System.out.println("我是哲学家" + this.id + "号,我拿到俩只筷子!");
    }

    //放筷子
    public void putDown(Chopsticks chopsticksLeft, Chopsticks chopsticksRight) {
        synchronized (Test3.locker) {
            chopsticksLeft.used = false;
            chopsticksRight.used = false;
            System.out.println("我是哲学家" + this.id + "号,我吃完了!");
            Test3.locker.notify();
        }
    }
}

//筷子
class Chopsticks {
    //筷子编号
    public int id;
    //筷子状态
    public boolean used;

    public Chopsticks(int id, boolean used) {
        this.id = id;
        this.used = used;
    }
}

public class Test3 {
    public static Object locker = new Object();

    public static void main(String[] args) {
        Philosopher philosopher1 = new Philosopher(1);
        Philosopher philosopher2 = new Philosopher(2);
        Philosopher philosopher3 = new Philosopher(3);
        Philosopher philosopher4 = new Philosopher(4);
        Philosopher philosopher5 = new Philosopher(5);

        Chopsticks chopsticks1 = new Chopsticks(1, false);
        Chopsticks chopsticks2 = new Chopsticks(2, false);
        Chopsticks chopsticks3 = new Chopsticks(3, false);
        Chopsticks chopsticks4 = new Chopsticks(4, false);
        Chopsticks chopsticks5 = new Chopsticks(5, false);

        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher1.thinking();
                    philosopher1.takeUp(chopsticks1, chopsticks2);
                    philosopher1.eating();
                    philosopher1.putDown(chopsticks1, chopsticks2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher2.thinking();
                    philosopher2.takeUp(chopsticks2, chopsticks3);
                    philosopher2.eating();
                    philosopher2.putDown(chopsticks2, chopsticks3);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher3.thinking();
                    philosopher3.takeUp(chopsticks3, chopsticks4);
                    philosopher3.eating();
                    philosopher3.putDown(chopsticks3, chopsticks4);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher4.thinking();
                    philosopher4.takeUp(chopsticks4, chopsticks5);
                    philosopher4.eating();
                    philosopher4.putDown(chopsticks4, chopsticks5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher5.thinking();
                    philosopher5.takeUp(chopsticks5, chopsticks1);
                    philosopher5.eating();
                    philosopher5.putDown(chopsticks5, chopsticks1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        pool.shutdown();
    }
}

解决办法二,给筷子编号,哲学家将要进餐时,要先从小号(左边)的开始拿.

package thread4;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Philosopher {
    //哲学家编号
    public int id;

    public Philosopher(int id) {
        this.id = id;
    }

    //思考
    public void thinking() throws InterruptedException {
        System.out.println("我是哲学家" + this.id + "号,我正在思考!");
        Thread.sleep(1000);
    }

    //吃饭
    public void eating() throws InterruptedException {
        System.out.println("我是哲学家" + this.id + "号,我正在吃饭!");
        Thread.sleep(1000);
    }

    //拿筷子
    public void takeUp(Chopsticks chopsticksMin, Chopsticks chopsticksMax) throws InterruptedException {
        synchronized (Test4.locker) {
            //先尝试拿小号筷子,失败则进入等待状态,并释放锁
            if (!chopsticksMin.used) {//false能拿,true不能拿别人占用的了所以 是!chopsticksMin.used
                chopsticksMin.used = true;
                if (!chopsticksMax.used) {
                    chopsticksMax.used = true;
                } else {
                    Test4.locker.wait();
                }
            } else {
                Test4.locker.wait();
            }
        }
    }

    //放筷子
    public void putDown(Chopsticks chopsticksMin, Chopsticks chopsticksMax) {
        synchronized (Test4.locker) {
            chopsticksMin.used = false;
            chopsticksMax.used = false;
            System.out.println("我是哲学家" + this.id + "号,我吃完了!");
            Test4.locker.notify();
        }
    }
}

//筷子
class Chopsticks {
    //筷子编号
    public int id;
    //筷子状态
    public boolean used;

    public Chopsticks(int id, boolean used) {
        this.id = id;
        this.used = used;
    }
}

public class Test4 {
    public static Object locker = new Object();

    public static void main(String[] args) {
        Philosopher philosopher1 = new Philosopher(1);
        Philosopher philosopher2 = new Philosopher(2);
        Philosopher philosopher3 = new Philosopher(3);
        Philosopher philosopher4 = new Philosopher(4);
        Philosopher philosopher5 = new Philosopher(5);

        Chopsticks chopsticks1 = new Chopsticks(1, false);
        Chopsticks chopsticks2 = new Chopsticks(2, false);
        Chopsticks chopsticks3 = new Chopsticks(3, false);
        Chopsticks chopsticks4 = new Chopsticks(4, false);
        Chopsticks chopsticks5 = new Chopsticks(5, false);

        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher1.thinking();
                    philosopher1.takeUp(chopsticks1, chopsticks2);
                    philosopher1.eating();
                    philosopher1.putDown(chopsticks1, chopsticks2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher2.thinking();
                    philosopher2.takeUp(chopsticks2, chopsticks3);
                    philosopher2.eating();
                    philosopher2.putDown(chopsticks2, chopsticks3);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher3.thinking();
                    philosopher3.takeUp(chopsticks3, chopsticks4);
                    philosopher3.eating();
                    philosopher3.putDown(chopsticks3, chopsticks4);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher4.thinking();
                    philosopher4.takeUp(chopsticks4, chopsticks5);
                    philosopher4.eating();
                    philosopher4.putDown(chopsticks4, chopsticks5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    philosopher5.thinking();
                    philosopher5.takeUp(chopsticks5, chopsticks1);
                    philosopher5.eating();
                    philosopher5.putDown(chopsticks5, chopsticks1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        pool.shutdown();
    }
}

3.如何避免死锁

死锁产生的四个必要条件

  • 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  • 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  • 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。其中最容易破坏的就是 "循环等待".

破坏循环等待

最常用的一种死锁阻止技术就是锁排序. 假设有 N 个线程尝试获取 M 把锁, 就可以针对 M 把锁进行编号(1, 2, 3...M).N 个线程尝试获取锁的时候, 都按照固定的按编号由小到大顺序来获取锁. 这样就可以避免环路等待.

属于理论上的破除死锁的方法.但是这样的方法,并不实用.
更实用的方法,就是尽量避免锁嵌套.(不要在一个加锁的代码中,再去加其他锁...)

可能产生环路等待的代码:

package thread4;

public class Test2 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread thread1 = new Thread() {
            @Override
            public void run() {
                synchronized (locker1) {
                    synchronized (locker2) {

                    }
                }
            }
        };
        thread1.start();

        Thread thread2 = new Thread() {
            @Override
            public void run() {
                synchronized (locker2) {
                    synchronized (locker1) {

                    }
                }
            }
        };
        thread2.start();
    }
}

约定好先获取 lock1, 再获取 lock2 , 就不会环路等待(锁排序)

package thread4;

public class Test2 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread thread1 = new Thread() {
            @Override
            public void run() {
                synchronized (locker1) {
                    synchronized (locker2) {

                    }
                }
            }
        };
        thread1.start();

        Thread thread2 = new Thread() {
            @Override
            public void run() {
                synchronized (locker1) {
                    synchronized (locker2) {

                    }
                }
            }
        };
        thread2.start();
    }
}






 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/345298.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网际协议IP

网际协议IP 文章目录网际协议IP[toc]虚拟互联网IP地址及其表示方法分类IP地址(两级)无分类编址 CIDR网路前缀地址块地址掩码子网划分(三级IP地址)IP地址和MAC地址地址解析协议ARPIP数据报的格式IP数据报首部的固定部分中的各字段IP数据报首部的可变部分分…

REDIS-雪崩、击穿、穿透

直接发车🚗 一.雪崩 1.触发原因 A.大量缓存数据在同一时间过期(失效) B.redis故障宕机 上述均导致全部请求去访问数据库,导致DB压力骤增,严重则导致数据库宕机/系统宕机 2.应对策略 不同触发原因,应对策略也不一致 应对A&a…

C# SolidWorks二次开发 API-命令标签页的切换与按钮错乱问题

这是一个网友咨询的问题,说他想控制默认打开文件之后solidworks上方工具栏的当前激活标签页。 之前我们提到过,制作Solidworks的插件也会在上面增加一个标签页,用来放自己开发的命令,经常开发的人肯定会遇到有时候更新版本,或者标…

奥威软件宏昊化工启动BI项目,打造智能制造标杆

近日,中国纺织行业领先企业宏昊化工有限公司成功启动了与奥威签订的BI项目,期望通过BI的建立进一步提升企业数字化经营能力和核心竞争力。 奥威bi数据分析软件 在全球经济形势不明朗,国内外市场竞争加剧叠加疫情反复的情况下,化工…

初学vector

目录 string的收尾 拷贝构造的现代写法: 浅拷贝: 拷贝构造的现代写法: swap函数: 内置类型有拷贝构造和赋值重载吗? 完善拷贝构造的现代写法: 赋值重载的现代写法: 更精简的现代写法&…

1.5配置NBMA和P2MP网络类型

1.3.3实验5:配置NBMA和P2MP网络类型 1. 实验需求 控制OSPF DR的选举修改OSPF的网络类型2. 实验拓扑 配置NBMA和P2MP网络类型实验拓扑如图1-13所示。 图1-13 配置NBMA和P2MP网络类型 3. 实验步骤 帧中继的配置如图1-14和图1-15所示

软件测试——测试用例之场景法

一、场景法的应用场合 场景法主要用于测试软件的业务流程和业务逻辑。场景法是基于软件业务的测试方法。在场景法中测试人员把自己当成最终用户,尽可能真实的模拟用户在使用此软件的操作情景: 重点模拟两类操作: 1)模拟用户正确…

仓库拣货标签应用案例

使用场景:富士康成都仓库 解决问题:仓库亮灯拣选, 提高作业效率和物料明晰展示仓库亮灯拣选使用场景:京东仓库 解决问题:播种墙分拣,合单拣货完成后按订单播种播种墙分拣使用场景:和尔泰智能料…

Ubuntu 20中安装SNAP

Ubuntu 20中安装SNAP0 前言1 下载SNAP安装包2 安装SNAP详细步骤0 前言 姊妹篇《Ubuntu 20中安装snaphu》 SNAP是欧空局领导开发的开源的遥感数据处理软件,主要支持欧空局的数据,如sentinel-系列等。SNAP下载官网:https://step.esa.int/main…

算法训练营 day46 动态规划 最后一块石头的重量 II 目标和 一和零

算法训练营 day46 动态规划 最后一块石头的重量 II 目标和 一和零 最后一块石头的重量 II 1049. 最后一块石头的重量 II - 力扣(LeetCode) 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xf…

百度百科创建词条教程合集分享,赶紧收藏起来

每一个企业、品牌、人物、产品想要提升自己的知名度,都要创建一个属于自己的百度百科词条,互联网时代,百度搜索引擎的地位是不可撼动的,每天都有上亿的用户在百度上搜索相关内容,百度百科词条在网络营销中占据着举足轻…

Oracle对象——视图之简单视图与视图约束

文章目录什么是视图为什么会使用视图视图语法案例简单视图的创建更改数据基表,视图数据会变化么?更改视图数据,基表数据会变更么?带检查约束的视图结论创建只读视图(MySQL不支持)总结什么是视图 视图是一种…

【项目精选】高校固定资产管理系统(论文+视频+源码)

点击下载源码 随着计算机信息技术的发展以及对资产、设备的管理科学化、合理化的高要求,利用计算机实现设备及资产的信息化管理已经显得非常重要。 固定资产管理系统是一个单位不可缺少的部分。但一直以来人们使用传统的人工方式管理固定资产的信息,这种…

C++STL剖析(六)—— set和multiset的概念和使用

文章目录🌟 前言🍑 树型结构和哈希结构🍑 键值对1. set的介绍和使用🍑 set的模板参数列表🍑 set的构造🍑 set的使用🍅 insert🍅 find🍅 erase🍅 swap&#x1…

Java基础之异常

目录1 异常1.1 异常的概述1.2 常见异常类型1.3 JVM的默认处理方案1.4 编译时异常的处理方式1.4.1 异常处理之 try ... catch ... [ktʃ](捕获异常)1.4.2 异常处理之 throws(抛出异常)1.5 Throwable 的成员方法1.6 编译时异常和运行…

H5 抽奖页面

好久之前就想去写一个这样的示例了,然后就忘了…😵 😀效果 🖇在线链接 https://linyisonger.github.io/H5.Examples/?name./34.%E6%8A%BD%E5%A5%96%E9%A1%B5%E9%9D%A2.html 🚧 图片可能会丢失,都是在网…

大尺度衰落与小尺度衰落

一. 大尺度衰落 无线电磁波信号在收发天线长距离(远大于传输波长)或长时间范围发生的功率变化,称为大尺度衰落,一般可以用路径损耗模型来描述,路径损耗是由发射功率在空间中的辐射扩散造成的,根据功率传输…

Hadoop开启Yarn的日志监控功能

1.开启JobManager日志 &#xff08;1&#xff09;编辑NameNode配置文件${hadoop_home}/etc/hadoop/yarn-site.xml和mapred-site.xml 编辑yarn-site.xml <!-- Site specific YARN configuration properties --> <configuration><property><name>yarn.…

如何排查网页在哪里发生了内存泄漏?

今天我们来学习用 devtool 的 Performance 和 Memory 工具来找出网页哪里发生了内存泄漏。 Performace 面板 首先我们打开浏览器的 devtool&#xff0c;选择 Performance&#xff08;性能&#xff09;面板&#xff0c;然后将 Memory 选项勾选上。不勾选的话&#xff0c;就不会…

火爆全网的ChatGPT,可以自己上手搭建了。

没有人不知道ChatGPT了吧&#xff1f; ChatGPT&#xff0c;发布于2022年11月30日&#xff0c;来自人工智能研究实验室OpenAI&#xff0c;是一款全新聊天机器人模型&#xff0c;一款人工智能技术驱动的自然语言处理工具。 5天用户破百万&#xff0c;2个月活跃用户破亿。ChatGP…