【问题标题】:Dining Philosopher's solution ending up in deadlock餐饮哲学家的解决方案陷入僵局
【发布时间】:2017-04-10 11:52:00
【问题描述】:

我已经为餐饮哲学家的问题实施了资源层次结构解决方案。当我尝试比较两个筷子的 n 值时,它们最终陷入僵局。但是,如果我使用他们的 hashCodes 而不是 n 值,它运行顺利。为什么会有这种差异?一天结束时他们不是两个数字吗?

import java.util.Random;

class Chopstick {
    public final int n;
    public Chopstick(int n) {
        this.n = n;
    }
}

class Philosopher extends Thread {
    private Chopstick left, right;
    private Random random;
    private final int n;

    public Philosopher(int n, Chopstick left, Chopstick right) {
        this.n = n;
        if (left.n > right.n) { // no deadlock if I replace this with left.hashCode() > right.hashCode()
            this.right = left;
            this.left = right;
        } else {
            this.left = left;
            this.right = right;
        }
        this.random = new Random();
    }

    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(random.nextInt(10)); // Think
                synchronized(left) {
                    synchronized(right) {
                        System.out.println("P " + n + " eating");
                        Thread.sleep(random.nextInt(10));
                    }
                }
            }
        } catch(InterruptedException ie) {
            ie.printStackTrace();
        }
    }

}

class Main {
    public static void main(String[] args) {
        final int n = 3;
        Chopstick[] sticks = new Chopstick[n];
        Philosopher[] ps = new Philosopher[n];
        for (int i = 0; i < n; i++) {
            sticks[i] = new Chopstick(n);
        }
        for (int i = 0; i < n; i++) {
            ps[i] = new Philosopher(i, sticks[i], sticks[(i + 1) % n]);
            ps[i].start();
        }
    }
}

【问题讨论】:

  • Chopstick 类是否覆盖了 hashCode?​​span>
  • @NickVanderhoven 没有。这是完整的代码。
  • 只是一个风格注释:您有一个名为“left”的变量,它可以指代哲学家左侧的筷子,也可以指代哲学家右侧的筷子。对于名为“right”的变量也是如此。将变量命名为“第一”和“第二”会更有意义,其中的名称描述了哲学家选择它们的顺序。

标签: java multithreading dining-philosopher


【解决方案1】:

您的问题与您没有管理 left.n == right.n 的情况有关,不幸的是,您没有使用 sticks[i] = new Chopstick(i) 初始化您的 Chopstick 数组,而是使用了 sticks[i] = new Chopstick(n),因此您只有 @ 类型的情况987654328@ 没有得到妥善管理,所以你会遇到死锁。

由于您没有覆盖方法 hashCode(),因此使用 hashCode() 有助于避免该问题,因为它们是 Chopstick 的不同实例,具有不同的 hashCode() 值,但您仍然可能遇到我们有 2 个不同的情况Chopstick 的实例具有相同的 hashCode()。所以你仍然需要处理我们具有相同价值的情况。

正确管理相等值的方法是使用第三个锁,称为“tie break”锁

class Philosopher extends Thread {

    // The tie breaking lock
    private static Object tieLock = new Object();
    ...

    private void printAndSleep() throws InterruptedException {
        synchronized(left) {
            synchronized(right) {
                System.out.println("P " + n + " eating");
                Thread.sleep(random.nextInt(10));
            }
        }
    }

    public void run() {
        ...
        if (left.n == right.n) {
            // Equal values so we need first to acquire the tie breaking lock
            synchronized (tieLock) {
                printAndSleep();
            }
        } else {
            printAndSleep();
        }
        ...
    }
}

管理锁排序的一种更通用的方法是依靠System.identityHashCode(obj) 作为每个实例的值来排序,而不是使用字段的值或hashCode(),因为这样你就不会依赖于目标对象的特定内容输入。

Java Concurrency in PracticeBrian GoetzBrian Goetz10.1.2 动态锁顺序死锁一章

中有更多详细信息

【讨论】:

  • “避免“动态锁定顺序死锁”的更通用方法是使用 System.identityHashCode(obj)”不,不是。永远不要期望任何类型的哈希是唯一的,不能保证会是这样。这显然也是不可能的:哈希码只是一个 int,所以在 64 位 VM 上它不可能是唯一的。
【解决方案2】:

BUG是你有

sticks[i] = new Chopstick(n); 

应该是什么时候

sticks[i] = new Chopstick(i);

对象的哈希值仍然是唯一的,即使它们的数据相同,因为您没有覆盖 hashCode 函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-04
    • 2016-03-04
    • 1970-01-01
    • 2011-07-09
    • 2022-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多