0

我有以下代码,两个线程检查堆栈中是否有元素,如果有,则将其弹出。由于某种原因,两个线程之一总是得到一个 java.util.EmptyStackException ,这意味着它弹出一个空堆栈。我的问题是不应该让这种方法同步来防止这种情况发生吗?是否可以仅使用同步方法来防止我的错误,或者我必须实现某种连接/倒计时/循环障碍/等?我问的原因是因为理想情况下我希望只有一个线程能够在任何给定时间访问堆栈板。

public synchronized void checkPlate(){
        //System.out.println(this.name + " checks the plate for pancake");
        Boolean isEmpty = Producer.plate.isEmpty();
        if ( isEmpty == false){
            System.out.println(this.name + " grabs a pancake");
            Producer.plate.pop();
            eat();
            this.eatenPancakes +=1;
        }else{

            try {
                Thread.sleep(200);// waits one minute seconds instead of minutes
                checkPlate();
            } catch(InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }

更新
建议后,我决定尝试使用此代码在生产者板上实现同步块。这样做后,看起来我所有的线程都冻结了。这个同步块是否也需要添加到我的生产者对象/线程中?

 public void checkPlate(){
        //System.out.println(this.name + " checks the plate for pancake");
        synchronized (Producer.plate){
            Boolean isEmpty = Producer.plate.isEmpty();
            if ( isEmpty == false){
                System.out.println(this.name + " grabs a pancake");
                Producer.plate.pop();
                eat();
                this.eatenPancakes +=1;
            }else{

                try {
                    Thread.sleep(200);// waits since theres no pancake
                    checkPlate();
                } catch(InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    } 

更新 2
目标以提供更多信息。在每个线程抓取一个煎饼后,它会使用以下方法随机吃/睡一段时间。理想情况下,整个程序的目标是模拟生产者不断烹饪煎饼的位置,并将其添加到堆栈中。两个线程之一检查堆栈并在堆栈中有元素时抓取煎饼。这将一直发生,直到生产者线程完成煎饼。

public void eat(){
        Random ran = new Random();
        double eatingTime;
        if (this.name == "Piggy"){
            eatingTime = ran.nextDouble() * (4 - 2) + 2; //generates a value between 2 and 4
        }else{
            eatingTime = ran.nextDouble() * (5 - 3) + 3; //generates a
        }
        try {
            System.out.println(this.name + " starts eating a pancake...");
            Thread.sleep((long)eatingTime *100);//mili seconds instead of minutes
            Boolean isEmpty = Producer.plate.isEmpty();
            if (Producer.finished == false && isEmpty == false){
                checkPlate();
            }
        } catch(InterruptedException ex) {
            Thread.currentThread().interrupt();
        }
4

2 回答 2

4

我的问题是不应该让这种方法同步来防止这种情况发生吗?

基本上,你没有给我们足够的信息。但是如果有两个包含这个方法的类的实例,那么绝对不会。它们将有不同的监视器,因此可以同时在同步方法中。

看起来您正在使用静态变量 ( Producer.plate) 作为共享队列 - 在这种情况下,实例级同步基本上不会对您有所帮助,因为可能有多个实例checkPlate被调用。

无论如何,同步可能是不必要的。从根本上说,您应该使用其中的一个集合java.util.concurrent——这些集合是为并发访问而设计的,非常适合生产者/消费者队列。

此外,当盘子是空的时,您当前的代码当前会越来越深地递归 - 这不是一个好主意。如果您希望它继续执行直到它消耗了一个项目,您应该使用while循环而不是递归。

于 2013-02-26T07:26:12.297 回答
1

不幸的是,您没有显示调用部分。最重要的是,我们必须看看这个方法是在同一个对象上调用还是在不同对象上调用。

如果它从共享同一资源(生产者)的 2 个不同对象调用该方法,那么您就有问题了。

看起来这就是正在发生的事情。

我也认为你还有另一个问题。如果你的栈足够大,你的递归会导致栈溢出异常。


澄清后更新

从本质上讲,同步方法与将监视器作为对象本身的同步块相同:

synchronized(this) {
    // code
}

由于您有 2 个单独的对象,因此它们的“this”不是同一个对象,它们实际上并没有锁定您的资源。

锁定资源的简单经典准则是访问必须由共享资源同步。由于您共享 Producer.plate,您可以执行以下操作:

synchronized(Producer.plate) {
    // code
}

其他对象也可以用作监视器,但开发人员有责任确保它们得到适当的管理,以确保对 Product.plate 的同步访问。


更新以提供重做方法,该方法检查生产者是否完成烹饪而没有递归。

public void checkPlate(){
    while(!Producer.finished)
    //System.out.println(this.name + " checks the plate for pancake");
    synchronized (Producer.plate){
        Boolean isEmpty = Producer.plate.isEmpty();
        if ( isEmpty == false){
        System.out.println(this.name + " grabs a pancake");
        Producer.plate.pop();
        eat();
        this.eatenPancakes +=1;
        }
    }
    try {
        Thread.sleep(200);// waits since theres no pancake
    } catch(InterruptedException ex) {
        Thread.currentThread().interrupt();
    }
}
} 
于 2013-02-26T07:27:01.150 回答