在 Java 穿线(为大学实习)

Threading in Java (practicing for college)

Create a program that simulates training at an athletic stadium, there is one track in the stadium that can be used by up to 5 people at a time and the coach does not allow that number to exceed, but when some of the athletes finish their run (2sec) and free up space then notify other athlete for running.

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) {
            }
        }
    }
}

这有很多问题。

你的方法用错地方了。 synchronized 关键字在 class 的一个实例上同步,而不是跨多个实例。因此,您在不同运动员身上删除和添加功能会导致比赛条件。这些功能应移至 Track 对象,因为所有运动员都使用相同的跑道(您的 isThereSpace 功能也应如此)。同时,在Athlete中不应该直接访问Track的成员变量,而是使用getter代替。

其次,你对wait和notify的使用是错误的。他们为竞争条件留下了很多漏洞,尽管它可能在大多数时候都有效。这并不是使用它们的好地方——Track class 中的计数信号量将是更好的解决方案——这正是计数信号量的用途。查看信号量 class 了解更多详细信息。它基本上是一个锁,一次允许 N 个所有者拥有锁,并阻止其他请求者直到所有者释放它。

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

解决此问题的一种方法是让所有运动员在同一对象上 synchronize/wait/notify,例如 JoggingTrack。这样一个运动员会在跑道上等待track.wait(),当一个运动员完成后运行,它会调用track.notify(),然后一个等待的运动员会被唤醒。

还有 Gabe 指出的其他问题-

一旦你解决了第一个问题,你就会发现竞态条件——例如。太多线程全部启动 运行 即使有一些检查 (thereIsSpace) 到位。

My question is, could anyone explain to me why something like this does not work and how to handle this problem?

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

在你的情况下,你混淆了你的对象。想一想 Athlete.thereIsSpace()Athlete.addAthlete(...)。这有任何意义吗?运动员有space吗?你给运动员加运动员吗?有时对象名称不能帮助您进行此类评估,但在这种情况下,它们可以。是 JoggingTrack 有 space 并且添加了运动员。

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

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

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

此外,与 ++ 案例一样,您需要非常小心代码中的竞争条件。不,不是慢跑,而是 programming races。例如,如果 2 名运动员在 恰好同时 执行以下逻辑会发生什么:

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

两位运动员都可能称 thereIsSpace() return 正确(因为尚未添加任何人)。然后两者都继续并将自己添加到轨道中。这将使运动员人数增加 2 人,并可能超过 5 人的限制。除非你在 synchronized 块中,否则每次都会发生这些类型的竞争条件。

JoggingTrack 可以使用如下代码:

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

那么健美运动员会做的:

track.addIfSpaceOrWait();
addAthlete();

此代码没有比赛条件,因为一次只有一名运动员将 synchronized 锁定在赛道上 -- java 保证。他们两个可以同时调用它,一个会 return 而另一个会等待。

其他一些随机评论:

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