【问题标题】:Java automatically recognizes thread deadlock and stops execution?Java自动识别线程死锁并停止执行?
【发布时间】:2013-09-28 20:30:03
【问题描述】:

我正在开发一个 Java 项目来模拟 dining philosophers problem

我从基本行为开始,每个线程 think()、getForks()、eat() 和 putForks()。因此无法防止死锁或饥饿(故意这样做)。

getForks() 方法的工作原理如下:

getForks(){
    while(forks[rightFork]==0) /*0 means fork is not on the table, so wait*/
         print(Thread #id waiting for right fork);
    forks[rightFork] = 0;
    while(forks[leftFork]==0)
         print(Thread #id waiting for left fork);
    forks[leftFork = 0;
}

我在获得右叉和左叉之间设置了一个 sleep(5000),因此程序陷入死锁(每个线程都持有右叉)。然而,出乎意料的是,由于某种原因,一旦到达死锁,执行就会停止。我预计在死锁期间会继续打印“Thread #id waiting for fork”消息,但事实并非如此。一旦达到死锁,就不会再打印消息了。

有什么线索吗?

如果你想查看整个代码,这里是:

public class Philosophers{
    private static final int NUMBER = 3;
    private static final int MIN_SLEEP = 1000;
    private static final int MAX_SLEEP = 6000;
    private static Thread[] threads = new Thread[NUMBER];
    private static int[] forksArray = new int[8];

    private static void start(){
        System.out.println("Simulation started.");

        //Initialize forks array (1 means on the table)
        for(int i=0;i<7;i++)
            forksArray[i] = 1;

        //Create and start individual threads
        for(int i=0;i<NUMBER;i++){
            threads[i] = new Thread(new Philosopher(i));
            threads[i].start();
        }
    }

    public static void main(String[] args){
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                start();
            }
        });
    }

    private static class Philosopher implements Runnable{
        private int id;
        private int leftFork;
        private int rightFork;

        public void run(){
            System.out.println("Thread "+id+" started.");
            while(true){
                think();
                getForks();
                eat();
                putForks();
            }
        }

        public Philosopher(int id){
            this.id = id;
            this.rightFork = id;
            if(id==NUMBER - 1)
                this.leftFork = 0;
            else
                this.leftFork = id + 1;
        }

        public void think(){
            System.out.println("Thread "+id+" thinking...");
            try{
                int sleepInterval = MIN_SLEEP + (int)(Math.random() * ((MAX_SLEEP - MIN_SLEEP) + 1));
                Thread.sleep(sleepInterval);
            }
            catch(Exception e){
                System.out.println(e);
            }
        }

        public void getForks(){
            System.out.println("Thread "+id+" getting forks.");

            //Grab fork on the right
            while(forksArray[rightFork]==0)
                System.out.println("Thread "+id+" waiting for right fork");
            forksArray[rightFork] = 0;  

            try{
            Thread.sleep(5000);
        }   
        catch(Exception e){

        }

            //Grab fork on the left
            while(forksArray[leftFork]==0);
            System.out.println("Thread "+id+" waiting for left fork");
            forksArray[leftFork] = 0;           
        }

        public void eat(){
            System.out.println("Thread "+id+" eating.");
            try{
                Thread.sleep(2000);
            }
            catch(Exception e){
                System.out.println(e);
            }
        }

        public void putForks(){
            System.out.println("Thread "+id+" putting forks down.");
            forksArray[rightFork] = 1;
            forksArray[leftFork] = 1;
        }
    }
}

【问题讨论】:

  • 不看实际代码就无法判断——你使用锁吗?同步块?
  • @assylias,给你。

标签: java multithreading concurrency deadlock


【解决方案1】:

我认为你的问题在这里:

while(forksArray[leftFork]==0);
System.out.println("Thread "+id+" waiting for left fork");

;在while() 之后意味着它有一个空语句作为它的主体。因此,下一行的println 只会在循环终止时发生。

(顺便说一句,这就是为什么人们经常建议始终使用 {} for 循环,即使它们是单行的)

【讨论】:

  • 那行很好:它的意思是等到值变为 != 0。并且该值是从另一个线程更新的
  • @assylias 是的,但问题是为什么没有打印消息。那是因为 OP 认为 println 是循环的主体,但实际上是 ;是循环体。
  • @Russell Zahniser,你成功了。基本上,由于那个额外的';',消息没有被打印出来。谢谢。
【解决方案2】:

这并不是真正的死锁:你没有使用任何锁!这只是一个无限循环。

一个问题是您没有使用正确的同步。特别是编译器可以随意更换:

while (forksArray[leftFork] == 0);

与:

int temp = forksArray[leftFork];
while (temp == 0);

这可以在程序执行期间的任何时候发生。

让你的数组变得易变:

private static volatile int[] forksArray = new int[8];

应该让这个问题消失。但请注意,将数组标记为 volatile 只会阻止执行优化 - 不足以使代码线程安全。

【讨论】:

  • 我的问题不是如何避免陷入死锁(我知道如何为此目的使用锁)。我的问题是,一旦到达该点,消息如何停止打印。即使编译器替换了您建议的代码,您是否同意在线程继续等待分叉时继续打印消息?此外,即使您使用“易失性”,您仍然会遇到我在上面粘贴的代码的死锁。 Volatile 只告诉变量不应该放在寄存器中进行优化。
  • @DanielS 抱歉,我引用了错误的循环 - 请参阅我的编辑。当我添加 volatile 时,我不再观察您提到的行为。
  • 使数组易失不会使修改元素具有易失语义!您可以使用 AtomicIntegerArray 来执行此操作。然而,这并不能解决缺乏原子性的问题。
  • @Assylias,我更新了代码,在获得右叉和左叉之间包含一个 sleep(5000)。添加该睡眠,您将看到死锁和不再打印的消息。
  • @jtahlborn 我知道 - 但这足以防止导致问题的优化。我添加了一个注释来澄清。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多