哲学家进餐问题(Java代码实现)

目录

一,哲学家就餐问题详解Lock + Java代码实现(逐行讲解)

一、哲学家就餐问题简介

二、Java多线程基础知识

三、完整代码实现(逐行讲解)

四、代码逐行解析

1. 创建筷子(锁对象)

2. 创建哲学家线程

3. 哲学家类(核心逻辑)

五、如何避免死锁?

六、运行结果示例

七、新手常见疑问解答

二,使用 synchronized 实现哲学家就餐问题(逐行详解)

一、实现思路

二、完整代码实现

三、代码逐行解析

1. 创建筷子(对象锁)

2. 创建哲学家线程

3. 哲学家类(核心逻辑)

四、如何避免死锁?

五、运行结果示例

六、synchronized vs ReentrantLock

七、新手常见疑问解答

三,使用信号量(Semaphore)解决哲学家就餐问题(逐行详解)

一、实现思路

二、完整代码实现

三、代码逐行解析

1. 创建筷子(信号量)

2. 全局信号量限制并发

3. 哲学家线程逻辑

四、如何避免死锁?

五、运行结果示例

六、信号量 vs 锁

七、新手常见疑问解答


一,哲学家就餐问题详解(ReentrantLock)代码实现(逐行讲解)


一、哲学家就餐问题简介

问题场景
5位哲学家围坐在圆桌旁,每人面前有一碗饭,左右各有一根筷子。哲学家的行为模式是:

  1. 思考 → 2. 拿起左右筷子吃饭 → 3. 放下筷子继续思考

核心矛盾
如果所有哲学家同时拿起左边的筷子,所有人都会等待右边的筷子,导致死锁(Deadlock),即程序永远卡住。


二、Java多线程基础知识
  1. 线程(Thread):程序中的执行单元,可以同时运行多个线程。

  2. Runnable接口:Java中定义线程任务的接口,需实现run()方法。

  3. 锁(Lock):通过ReentrantLock类实现,保证同一时间只有一个线程访问资源。

  4. java Lock锁详解-优快云博客


三、完整代码实现(逐行讲解)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DiningPhilosophers {

    public static void main(String[] args) {
        // 创建5根筷子(锁对象)
        Lock[] chopsticks = new Lock[5];
        for (int i = 0; i < 5; i++) {
            chopsticks[i] = new ReentrantLock();
        }

        // 创建5个哲学家并启动线程
        Philosopher[] philosophers = new Philosopher[5];
        for (int i = 0; i < 5; i++) {
            // 分配左右筷子,最后一人的右筷子是第一个人的左筷子(环形)
            Lock leftChopstick = chopsticks[i];
            Lock rightChopstick = chopsticks[(i + 1) % 5];
            philosophers[i] = new Philosopher(i, leftChopstick, rightChopstick);
            new Thread(philosophers[i]).start(); // 启动线程
        }
    }

    static class Philosopher implements Runnable {
        private final int id; // 哲学家编号
        private final Lock leftChopstick;
        private final Lock rightChopstick;

        public Philosopher(int id, Lock leftChopstick, Lock rightChopstick) {
            this.id = id;
            this.leftChopstick = leftChopstick;
            this.rightChopstick = rightChopstick;
        }

        @Override
        public void run() {
            try {
                while (true) { // 无限循环模拟哲学家的持续行为
                    // 1. 思考
                    System.out.println("哲学家 " + id + " 正在思考...");
                    Thread.sleep(1000); // 思考1秒

                    // 2. 尝试拿筷子(避免死锁的关键)
                    if (leftChopstick.tryLock()) { // 尝试获取左筷子
                        try {
                            if (rightChopstick.tryLock()) { // 尝试获取右筷子
                                try {
                                    // 3. 吃饭
                                    System.out.println("哲学家 " + id + " 正在吃饭...");
                                    Thread.sleep(1000); // 吃饭1秒
                                } finally {
                                    rightChopstick.unlock(); // 释放右筷子
                                }
                            }
                        } finally {
                            leftChopstick.unlock(); // 释放左筷子
                        }
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

四、代码逐行解析
1. 创建筷子(锁对象)

Lock[] chopsticks = new Lock[5];
for (int i = 0; i < 5; i++) {
    chopsticks[i] = new ReentrantLock(); // 每根筷子对应一个锁
}
  • Lock接口:表示锁,ReentrantLock是其实现类。

  • 作用:每根筷子同一时间只能被一个哲学家持有。


2. 创建哲学家线程

Philosopher[] philosophers = new Philosopher[5];
for (int i = 0; i < 5; i++) {
    Lock leftChopstick = chopsticks[i];
    Lock rightChopstick = chopsticks[(i + 1) % 5]; // 环形分配
    philosophers[i] = new Philosopher(i, leftChopstick, rightChopstick);
    new Thread(philosophers[i]).start(); // 启动线程
}
  • (i + 1) % 5:确保最后一个哲学家的右筷子是第一个人的左筷子,形成环形。

  • new Thread(philosophers[i]).start():创建线程并启动,执行run()方法。


3. 哲学家类(核心逻辑)

static class Philosopher implements Runnable {
    private final int id;
    private final Lock leftChopstick;
    private final Lock rightChopstick;

    public Philosopher(int id, Lock leftChopstick, Lock rightChopstick) {
        this.id = id;
        this.leftChopstick = leftChopstick;
        this.rightChopstick = rightChopstick;
    }

    @Override
    public void run() {
        try {
            while (true) { // 无限循环
                // 思考
                System.out.println("哲学家 " + id + " 正在思考...");
                Thread.sleep(1000);

                // 尝试拿筷子
                if (leftChopstick.tryLock()) { // 非阻塞尝试获取左筷子
                    try {
                        if (rightChopstick.tryLock()) { // 非阻塞尝试获取右筷子
                            try {
                                // 吃饭
                                System.out.println("哲学家 " + id + " 正在吃饭...");
                                Thread.sleep(1000);
                            } finally {
                                rightChopstick.unlock(); // 释放右筷子
                            }
                        }
                    } finally {
                        leftChopstick.unlock(); // 释放左筷子
                    }
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
  • tryLock():尝试获取锁,如果失败立即返回false(非阻塞),避免死锁。

  • finally:确保无论是否成功获取右筷子,最终都会释放左筷子。

  • Thread.sleep(1000):模拟思考和吃饭的时间。


五、如何避免死锁?
  1. 非阻塞获取锁:使用tryLock()而非lock(),如果获取不到立即释放已持有的锁。

  2. 按顺序获取资源:所有哲学家先拿编号较小的筷子(例如,编号为偶数的先左后右,奇数反之),破坏循环等待条件。

  3. 超时机制tryLock(timeout, unit),超过时间自动放弃。


六、运行结果示例

哲学家 0 正在思考...
哲学家 1 正在思考...
哲学家 2 正在思考...
哲学家 3 正在思考...
哲学家 4 正在思考...
哲学家 0 正在吃饭...
哲学家 2 正在吃饭...
哲学家 0 正在思考...
哲学家 2 正在思考...
哲学家 1 正在吃饭...
...
  • 观察:哲学家们交替吃饭,不会全部卡住。


七、新手常见疑问解答
  1. 为什么用ReentrantLock而不是synchronized

    • ReentrantLock更灵活,支持tryLock()和超时,适合复杂场景。

  2. tryLock()的参数是什么?

    • 无参版本立即返回是否成功;有参版本可设置等待时间。

  3. 如何停止程序?

    • Ctrl+C终止控制台运行。


通过这个案例,你不仅学会了哲学家问题的解决,还掌握了Java多线程的基本用法! 🚀

可以使用sychorized来实现一下吗

已深度思考(用时 29 秒)

好的,用户现在问是否可以使用`synchronized`关键字来实现哲学家就餐问题。之前我已经给出了基于`ReentrantLock`和`tryLock()`的解决方案,现在需要调整代码使用`synchronized`。首先,我需要回忆一下`synchronized`的用法,以及如何避免死锁。

用户可能是新手,已经学习了之前的例子,现在想对比不同实现方式。他们可能对`synchronized`和`ReentrantLock`的区别不太清楚,或者想了解传统方法如何处理这个问题。需要确保解释清楚,并且代码容易理解。

首先,哲学家就餐问题的核心在于死锁。使用`synchronized`的话,每个筷子可以作为一个对象锁。哲学家需要获取左右两个筷子的锁才能进餐。但直接按顺序获取可能会导致死锁,比如所有哲学家都先拿左边的筷子,然后等待右边,形成循环等待。

为了避免死锁,常见的策略是破坏循环等待条件。比如让部分哲学家改变拿筷子的顺序,比如奇数编号的哲学家先拿右边的筷子,偶数编号的拿左边的。这样可以避免所有哲学家同时等待同一方向的资源。

接下来,我需要设计代码结构。每个筷子作为对象,用`synchronized`块来获取锁。哲学家线程在获取左右筷子锁的时候,按照编号决定顺序。

需要注意的是,`synchronized`是阻塞的,一旦锁被占用,线程会等待,直到锁释放。所以必须确保获取锁的顺序不会导致死锁,或者在获取锁时采用超时机制,但`synchronized`本身不支持超时,所以可能需要其他方法。

不过,使用顺序策略可能更简单。例如,让编号为偶数的哲学家先拿左筷子再拿右,而奇数编号的先拿右再左。这样就不会形成循环等待。

然后,编写代码时,每个哲学家线程尝试获取两个筷子的锁。使用嵌套的`synchronized`块,按照顺序获取。如果其中一个锁无法获取,线程会阻塞,但由于顺序不同,至少有一个哲学家能顺利拿到两个筷子,避免死锁。

此外,需要确保在获取锁之后正确释放,即使在异常情况下也要释放,但使用`synchronized`块会自动释放锁,所以不需要手动处理。

测试代码时,可能需要调整思考和进餐的时间,以便观察线程间的协调情况。同时,输出日志可以帮助用户理解每个哲学家的状态变化。

最后,需要解释代码的每一部分,确保用户理解`synchronized`的用法,以及如何通过改变获取锁的顺序来避免死锁。可能还需要比较`ReentrantLock`和`synchronized`的不同,说明为什么在这种情况下`synchronized`也能解决问题,但可能不如显式锁灵活。

二,使用 (synchronized) 实现哲学家就餐问题(逐行详解)


一、实现思路

synchronized 是 Java 中基于对象监视器(Monitor)的同步机制,我们可以将每根筷子视为一个对象锁。通过破坏死锁的循环等待条件(例如让部分哲学家改变拿筷子的顺序),可以避免死锁。


二、完整代码实现

public class DiningPhilosophersSync {

    public static void main(String[] args) {
        // 1. 创建5根筷子(对象锁)
        Object[] chopsticks = new Object[5];
        for (int i = 0; i < 5; i++) {
            chopsticks[i] = new Object();
        }

        // 2. 创建5个哲学家并启动线程
        Philosopher[] philosophers = new Philosopher[5];
        for (int i = 0; i < 5; i++) {
            Object leftChopstick = chopsticks[i];
            Object rightChopstick = chopsticks[(i + 1) % 5];
            
            // 关键点:改变部分哲学家的拿筷子顺序,破坏循环等待
            if (i % 2 == 0) {
                philosophers[i] = new Philosopher(i, leftChopstick, rightChopstick);
            } else {
                philosophers[i] = new Philosopher(i, rightChopstick, leftChopstick);
            }
            
            new Thread(philosophers[i]).start();
        }
    }

    static class Philosopher implements Runnable {
        private final int id;
        private final Object firstChopstick;  // 第一个要拿的筷子
        private final Object secondChopstick; // 第二个要拿的筷子

        public Philosopher(int id, Object firstChopstick, Object secondChopstick) {
            this.id = id;
            this.firstChopstick = firstChopstick;
            this.secondChopstick = secondChopstick;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    // 1. 思考
                    System.out.println("哲学家 " + id + " 正在思考...");
                    Thread.sleep(1000);

                    // 2. 拿筷子(按顺序获取锁)
                    synchronized (firstChopstick) {
                        System.out.println("哲学家 " + id + " 拿起了第一根筷子");
                        synchronized (secondChopstick) {
                            // 3. 吃饭
                            System.out.println("哲学家 " + id + " 拿起了第二根筷子,开始吃饭...");
                            Thread.sleep(1000);
                        }
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

三、代码逐行解析
1. 创建筷子(对象锁)

Object[] chopsticks = new Object[5];
for (int i = 0; i < 5; i++) {
    chopsticks[i] = new Object(); // 每根筷子是一个对象锁
}
  • Object 作为锁:Java 中任意对象都可以作为 synchronized 的锁。


2. 创建哲学家线程

Philosopher[] philosophers = new Philosopher[5];
for (int i = 0; i < 5; i++) {
    Object leftChopstick = chopsticks[i];
    Object rightChopstick = chopsticks[(i + 1) % 5]; // 环形分配
    
    // 关键点:偶数编号哲学家先拿左筷子,奇数编号先拿右筷子
    if (i % 2 == 0) {
        philosophers[i] = new Philosopher(i, leftChopstick, rightChopstick);
    } else {
        philosophers[i] = new Philosopher(i, rightChopstick, leftChopstick);
    }
    
    new Thread(philosophers[i]).start(); // 启动线程
}
  • 破坏循环等待:通过让奇数编号的哲学家先拿右筷子,偶数编号先拿左筷子,确保至少有两个哲学家拿筷子的顺序不同,打破死锁条件。


3. 哲学家类(核心逻辑)

static class Philosopher implements Runnable {
    private final int id;
    private final Object firstChopstick;  // 第一个要拿的筷子
    private final Object secondChopstick; // 第二个要拿的筷子

    public Philosopher(int id, Object firstChopstick, Object secondChopstick) {
        this.id = id;
        this.firstChopstick = firstChopstick;
        this.secondChopstick = secondChopstick;
    }

    @Override
    public void run() {
        try {
            while (true) { // 无限循环
                // 1. 思考
                System.out.println("哲学家 " + id + " 正在思考...");
                Thread.sleep(1000);

                // 2. 按顺序拿筷子(通过synchronized嵌套)
                synchronized (firstChopstick) {
                    System.out.println("哲学家 " + id + " 拿起了第一根筷子");
                    synchronized (secondChopstick) {
                        // 3. 吃饭
                        System.out.println("哲学家 " + id + " 拿起了第二根筷子,开始吃饭...");
                        Thread.sleep(1000);
                    } // 自动释放第二根筷子
                } // 自动释放第一根筷子
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
  • synchronized 块:通过嵌套的 synchronized 保证同时持有两根筷子。

  • 锁的释放:退出 synchronized 块时自动释放锁,无需手动操作。


四、如何避免死锁?
  1. 破坏循环等待:通过改变部分哲学家的拿筷子顺序(如奇数编号先拿右筷子),确保不会所有人都在等待同一个方向的资源。

  2. 原子性操作synchronized 保证拿筷子的操作是原子的,不会被其他线程打断。


五、运行结果示例

哲学家 0 正在思考...
哲学家 1 正在思考...
哲学家 2 正在思考...
哲学家 3 正在思考...
哲学家 4 正在思考...
哲学家 0 拿起了第一根筷子
哲学家 0 拿起了第二根筷子,开始吃饭...
哲学家 2 拿起了第一根筷子
哲学家 2 拿起了第二根筷子,开始吃饭...
...
  • 观察:哲学家交替吃饭,不会全部卡住。


六、synchronized vs ReentrantLock
特性synchronizedReentrantLock
锁的获取自动释放,无需手动管理需要手动 lock() 和 unlock()
超时机制不支持支持 tryLock(timeout)
公平性非公平锁可设置为公平锁
灵活性简单场景适用复杂场景(如条件变量)

七、新手常见疑问解答
  1. 为什么用 Object 作为锁?

    • 因为 synchronized 可以基于任何对象,用 Object 是最轻量的选择。

  2. 如果去掉「改变拿筷子顺序」的逻辑会怎样?

    • 会导致所有哲学家同时拿左筷子,最终死锁!

  3. 为什么不需要 finally 释放锁?

    • synchronized 块结束后会自动释放锁,无需手动操作。


通过这个实现,你学会了如何用 synchronized 解决经典死锁问题! 🚀

使用信号量来解决

已深度思考(用时 35 秒)

嗯,用户之前已经询问了哲学家就餐问题的Java实现,分别用ReentrantLock和synchronized解决了。现在用户问的是“使用信号量来解决”,也就是用Semaphore来实现这个问题。我需要详细分析用户的需求和背景,确保给出合适的回答。

首先,用户可能是在学习多线程同步的不同方法,想了解信号量在解决这类问题中的应用。用户已经对问题本身有一定了解,所以不需要再重复解释问题,但可能需要代码实现的详细解释,尤其是针对新手的部分。

接下来,我需要考虑信号量(Semaphore)在Java中的使用方式。信号量可以用来控制对资源的访问,比如这里每根筷子作为一个资源,但通常信号量更适用于管理多个实例的资源。不过哲学家问题中的筷子是每根只能被一个哲学家持有,所以可能需要每个筷子对应一个信号量,或者采用其他方式。

不过,常见的解决方案是使用一个信号量来限制同时吃饭的哲学家数量,比如最多允许4个哲学家同时拿筷子,这样可以避免死锁。另一种方法是每个筷子作为一个信号量,初始化为1,这样每次拿筷子需要acquire,释放时release。但这样可能会有死锁风险,所以需要结合其他策略。

我需要选择一种有效且易于理解的信号量实现方式,并确保代码能够避免死锁。同时,要逐行解释代码,帮助新手理解信号量的使用和多线程的同步机制。

可能的实现步骤:

1. 为每根筷子创建一个信号量,初始值为1。

2. 哲学家拿筷子时先拿左边,再拿右边,但这样可能会导致死锁。

3. 为了避免死锁,可以引入一个额外的信号量,限制最多只有4个哲学家同时拿筷子,这样至少有一个哲学家可以拿到两根筷子。

4. 或者,让哲学家按特定顺序拿筷子,比如编号为偶数的先左后右,奇数的先右后左,类似之前的synchronized方案,但使用信号量来实现。

这里选择第一种方法,使用一个额外的信号量来限制同时拿筷子的哲学家数量。这样代码结构会更清晰,也容易解释。

然后,我需要编写代码,并用详细的注释解释每一步的作用,特别是信号量的acquire和release方法,以及如何避免死锁。此外,要确保代码结构清晰,变量命名明确,方便理解。

在解释过程中,要对比之前使用ReentrantLock和synchronized的解决方案,说明信号量在此场景中的优势和不同之处。同时,要提醒用户注意信号量的正确释放,避免资源泄漏。

最后,验证代码的正确性,确保没有死锁,并且所有哲学家都能正常交替进餐。可以通过模拟输出来展示结果,帮助用户理解程序的执行流程。

总结步骤:

1. 创建5个信号量代表筷子,每个初始值为1。

2. 创建一个额外的信号量,限制最多4个哲学家同时拿筷子。

3. 每个哲学家线程尝试获取额外信号量,然后按顺序获取左右筷子的信号量。

4. 进餐后按相反顺序释放筷子信号量,并释放额外信号量。

5. 解释代码中的关键点,如信号量的获取顺序、死锁避免策略等。

这样,用户能够通过信号量的方式解决哲学家就餐问题,并理解其背后的多线程同步机制。

三,使用信号量(Semaphore)解决哲学家就餐问题(逐行详解)


一、实现思路

信号量(Semaphore) 是一种计数器,用于控制对共享资源的访问。我们可以将每根筷子视为一个信号量(初始值为1),并通过一个额外的信号量限制同时拿筷子的哲学家数量,避免死锁。


二、完整代码实现

import java.util.concurrent.Semaphore;

public class DiningPhilosophersSemaphore {

    public static void main(String[] args) {
        // 1. 创建5根筷子(信号量,初始值为1)
        Semaphore[] chopsticks = new Semaphore[5];
        for (int i = 0; i < 5; i++) {
            chopsticks[i] = new Semaphore(1);
        }

        // 2. 创建一个全局信号量,限制最多4个哲学家同时拿筷子
        Semaphore maxDiners = new Semaphore(4);

        // 3. 创建5个哲学家并启动线程
        Philosopher[] philosophers = new Philosopher[5];
        for (int i = 0; i < 5; i++) {
            philosophers[i] = new Philosopher(
                i,
                chopsticks[i],          // 左筷子
                chopsticks[(i + 1) % 5], // 右筷子
                maxDiners
            );
            new Thread(philosophers[i]).start();
        }
    }

    static class Philosopher implements Runnable {
        private final int id;
        private final Semaphore leftChopstick;
        private final Semaphore rightChopstick;
        private final Semaphore maxDiners;

        public Philosopher(int id, Semaphore left, Semaphore right, Semaphore maxDiners) {
            this.id = id;
            this.leftChopstick = left;
            this.rightChopstick = right;
            this.maxDiners = maxDiners;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    // 1. 思考
                    System.out.println("哲学家 " + id + " 正在思考...");
                    Thread.sleep(1000);

                    // 2. 尝试获取就餐权限(最多4人)
                    maxDiners.acquire();

                    // 3. 拿左筷子
                    leftChopstick.acquire();
                    System.out.println("哲学家 " + id + " 拿起了左筷子");

                    // 4. 拿右筷子
                    rightChopstick.acquire();
                    System.out.println("哲学家 " + id + " 拿起了右筷子,开始吃饭...");

                    // 5. 吃饭
                    Thread.sleep(1000);

                    // 6. 放回右筷子
                    rightChopstick.release();
                    System.out.println("哲学家 " + id + " 放下了右筷子");

                    // 7. 放回左筷子
                    leftChopstick.release();
                    System.out.println("哲学家 " + id + " 放下了左筷子");

                    // 8. 释放就餐权限
                    maxDiners.release();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

三、代码逐行解析
1. 创建筷子(信号量)

Semaphore[] chopsticks = new Semaphore[5];
for (int i = 0; i < 5; i++) {
    chopsticks[i] = new Semaphore(1); // 初始值为1,表示每根筷子可用
}
  • Semaphore(1):每根筷子是一个二元信号量(只有0和1两种状态),表示是否可用。


2. 全局信号量限制并发

Semaphore maxDiners = new Semaphore(4); // 最多允许4人同时拿筷子
  • 关键设计:限制最多4个哲学家同时尝试拿筷子,确保至少有一个哲学家能拿到两根筷子,避免死锁。


3. 哲学家线程逻辑

public void run() {
    try {
        while (true) {
            // 思考
            System.out.println("哲学家 " + id + " 正在思考...");
            Thread.sleep(1000);

            // 获取就餐权限
            maxDiners.acquire(); // 如果已有4人在拿筷子,则等待

            // 拿左筷子
            leftChopstick.acquire(); // 如果左筷子被占用,则等待
            System.out.println("哲学家 " + id + " 拿起了左筷子");

            // 拿右筷子
            rightChopstick.acquire(); // 如果右筷子被占用,则等待
            System.out.println("哲学家 " + id + " 拿起了右筷子,开始吃饭...");

            // 吃饭
            Thread.sleep(1000);

            // 释放筷子(顺序与获取相反)
            rightChopstick.release();
            leftChopstick.release();

            // 释放就餐权限
            maxDiners.release();
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}
  • acquire():尝试获取信号量,若计数器为0则阻塞。

  • release():释放信号量,唤醒等待线程。

  • 释放顺序:先释放右筷子,再释放左筷子,避免死锁。


四、如何避免死锁?
  1. 全局信号量限制:最多允许4个哲学家同时拿筷子,确保至少有一人能完成就餐。

  2. 原子性操作:信号量的 acquire() 和 release() 是原子操作,保证线程安全。


五、运行结果示例

哲学家 0 正在思考...
哲学家 1 正在思考...
哲学家 2 正在思考...
哲学家 3 正在思考...
哲学家 4 正在思考...
哲学家 0 拿起了左筷子
哲学家 0 拿起了右筷子,开始吃饭...
哲学家 1 拿起了左筷子
哲学家 2 拿起了左筷子
哲学家 3 拿起了左筷子
哲学家 4 拿起了左筷子(等待,因为maxDiners=4已满)
...
哲学家 0 放下了右筷子
哲学家 0 放下了左筷子
哲学家 4 拿起了右筷子,开始吃饭...
  • 观察:哲学家交替就餐,无死锁。


六、信号量 vs 锁
特性信号量(Semaphore)锁(Lock)
资源数量可以管理多个资源(如允许N个线程访问)只能管理单个资源(互斥)
灵活性支持增减计数器,适合复杂同步逻辑简单的互斥访问
释放控制必须手动调用 release()synchronized 自动释放

七、新手常见疑问解答
  1. 为什么全局信号量设置为4?

    • 5个哲学家中最多允许4人同时拿筷子,确保至少有一人可以获得两根筷子,打破死锁循环。

  2. 如果忘记调用 release() 会怎样?

    • 信号量计数器无法恢复,导致资源泄漏,最终所有线程永久阻塞。

  3. 为什么释放顺序是先右后左?

    • 避免其他线程因拿筷子顺序不一致导致死锁(但在此方案中由于全局信号量的限制,顺序影响较小)。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值