我有以下代码,如果堆栈中有元素,则检查两个线程,如果是,则弹出它。由于某种原因,两个线程中的一个总是获得java.util.EmptyStackException,这意味着它弹出一个空堆栈。我的问题是不应该同步这个方法来防止这种情况吗?也可以只使用同步方法来防止我的错误,或者我必须实现某种join / countdownlatch / cyclicbarier / etc?我问的原因是因为理想情况下我希望只有一个线程能够在任何给定时间访问堆栈板。
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();
}
}
}
更新
建议后,我决定尝试使用此代码在生产者板块上实现一个同步块。这样做看起来我的所有线程都冻结了。是否需要将此synchronized块添加到我的生成器对象/线程中?
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();
}
答案 0 :(得分:4)
我的问题是不应该让这种方法同步防止这种情况?
基本上,你还没有给我们足够的信息。但是如果包含此方法的类有两个实例,那么绝对不会。它们将具有不同的监视器,因此可以同时在同步方法内部。
看起来就像你使用静态变量(Producer.plate
)作为共享队列一样 - 在这种情况下,实例级同步从根本上不会对你有所帮助,因为< em>可能是调用checkPlate
的多个实例。
尽管如此,同步可能是不必要的。从根本上说,您应该使用java.util.concurrent
中的一个集合 - 这些集合是设计的用于并发访问,非常适合生产者/消费者队列。
此外,您的当前代码目前将在盘子空的时候越来越深入 - 这不是一个好主意。如果你希望它在消耗一个项目之前继续执行,你应该使用while
循环而不是递归。
答案 1 :(得分:1)
不幸的是,您没有显示调用部分。最重要的是,我们必须查看是否在同一对象或不同对象上调用此方法。
如果它从共享相同资源(Producer)的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();
}
}
}