在Java中线程(为大学练习)



创建一个模拟运动场训练的程序, 体育场内有一条跑道,一次最多可容纳5人使用 教练不允许超过这个数字,但是当一些运动员完成跑步时(2秒) 并释放空间,然后通知其他运动员跑步。

2秒后,所有进程被冻结

我的问题是,谁能向我解释为什么这样的事情不起作用以及如何处理这个问题?

class JoggingTrack {
public int numOfAthlete;
public JoggingTrack() {
this.numOfAthlete = 0;
}
@Override
public String toString() {
return "nNumber of Athlete: " + numOfAthlete + "n";
}
}
class Athlete extends Thread {
private JoggingTrack track;
private boolean running;
public Athlete(JoggingTrack s) {
this.track = s;
this.running = false;
}
public synchronized boolean thereIsSpace() {
if(track.numOfAthlete < 5) {
return true;
}
return false;
}
public synchronized void addAthlete() {
track.numOfAthlete++;
this.running = true;
}
public synchronized void removeAthlete() {
track.numOfAthlete--;
this.running = false;
}
@Override
public void run() {
try {
while(true) {
while(!this.thereIsSpace()) {
wait();
}
while(!this.running) {
addAthlete();
sleep(2000);
} 
while(this.running) {
removeAthlete();
notify();
}
}
} catch (Exception e) {
}
}
}
public class Program {
static JoggingTrack track;
static Athlete[] a;
public static void main(String[] args) {
track = new JoggingTrack();
a = new Athlete[10];
for(int i = 0; i < 10; i++) {
a[i] = new Athlete(track);
a[i].start();
}
while(true) {
try {
System.out.println(track);
Thread.sleep(500);
} catch (Exception e) {
}
}
}
}

这有很多问题。

您的方法在错误的位置。 sync 关键字在类的实例上同步,而不是跨多个实例进行同步。 因此,您在不同运动员身上删除和添加功能会导致比赛条件。 这些函数应该移动到 Track 对象,因为所有运动员都使用相同的轨道(你的 isThereSpace 函数也应该如此)。 同时,您不应该直接访问运动员中 Track 的成员变量,而应为其使用 getter。

其次,你使用等待和通知是错误的。它们为比赛条件留下了很多漏洞,尽管它可能在大多数情况下都有效。 而且这不是使用它们的好地方 - Track 类中的计数信号量将是一个更好的解决方案 - 这正是计数信号量的用途。 查看信号量类以获取更多详细信息。 它基本上是一个锁,一次允许 N 个锁的所有者,并阻止其他请求者,直到所有者释放它。

你的线程永远在等待,因为它们在等待某个对象(它们的实例本身),没有人使用正确的实例notify它们。

解决此问题的一种方法是让所有运动员在同一对象上同步/等待/通知,例如JoggingTrack。这样运动员就会带着track.wait()在跑道上等待,当运动员跑完后,它会叫track.notify(),然后等待的运动员会被唤醒。

然后还有其他问题,正如Gabe所指出的那样——

解决第一个问题后,您将找到竞争条件 - 例如,即使有一些检查(thereIsSpace),太多的线程都开始运行。

我的问题是,谁能向我解释为什么这样的事情不起作用以及如何处理这个问题?

调试多线程程序很困难。 线程转储可能会有所帮助,println调试也可能有所帮助,但它们可能会导致问题迁移,因此应谨慎使用。

在您的情况下,您混淆了您的对象。 想一想Athlete.thereIsSpace()Athlete.addAthlete(...). 这有什么意义吗? 运动员有空间吗? 您是否将运动员添加到运动员中? 有时对象名称不能帮助您进行此类评估,但在这种情况下,它们确实如此。 这是有空间并添加运动员的JoggingTrack

当您处理多个线程时,您需要担心数据共享。 如果一个线程track.numOfAthlete++;,其他线程将如何看到更新? 默认情况下,它们不共享内存。 此外++实际上是 3 个操作(读取、增量、写入),您需要担心多个线程同时运行++。 您将需要使用synchronized块来确保内存更新或使用其他并发类,例如AtomicIntegerSemaphore来为您处理锁定和数据共享。 此外,更一般地说,您确实不应该以这种方式修改另一个对象的字段。

最后,您对wait/notify的工作方式感到困惑。 首先,它们只有在synchronized块或方法中时才有效,所以我认为您发布的代码无法编译。 在您的情况下,多个运动员争夺的是JoggingTrack,因此赛道需要具有synchronized关键字而不是AthleteAthlete正在等待JoggingTrack获得空间。 没有人在等待运动员。 像这样:

public class JoggingTrack {
public synchronized boolean thereIsSpace() {
return (numOfAthletes < 5);
}
public synchronized void addAthlete() {
numOfAthletes++;
}
...

此外,与++情况一样,您需要非常小心代码中的竞争条件。 不,不是慢跑比赛,而是编程比赛。 例如,如果两个运动员同时执行以下逻辑会发生什么:

while (!track.thereIsSpace()) {
track.wait();
}
addAthlete();

两位运动员都可能调用返回 true 的thereIsSpace()(因为尚未添加任何人)。 然后两者都继续并将自己添加到轨道中。 这将使运动员人数增加 2 人,甚至可能超过 5 人的限制。 这种种族条件每次都会发生,除非你处于synchronized块。

JoggingTrack可以包含如下代码:

public synchronized void addIfSpaceOrWait() {
while (numOfAthletes >= 5) {
wait();
}
numOfAthletes++;
}

然后阿尔特人会做:

track.addIfSpaceOrWait();
addAthlete();

此代码没有竞争条件,因为一次只有一个运动员会在赛道上获得synchronized锁 - java 保证这一点。 他们两个都可以同时打电话,一个会回来,另一个会等待。

其他几个随机评论:

  • 你永远不应该做catch (Exception e) {}. 仅仅做一个e.printStackStrace()就够糟糕的了,但是看不到你的错误真的会混淆你调试程序的能力。 我希望你只是为你的帖子这样做。 :-)
  • 我喜欢JoggingTrack对象名称,但无论何时引用它,它都应该是joggingTrack的,或者可能是track的。 小心JoggingTrack s
  • Athlete不应扩展线程。 它不是一个线程。 它应该实现Runnable. 这是一个常见问题解答。

最新更新