为什么 notifyAll() 没有唤醒这个例子中的所有线程?

Why is notifyAll() not waking all of the threads in this example?

我正在尝试弄清楚如何使用等待和通知,所以我写了这个小例子,其中有几架飞机在起飞前等待跑道清理,我遇到的问题是当一架飞机起飞并调用 notifyAll() 时,似乎只有一个线程被唤醒,即我希望所有线程都报告它们已收到通知,但仍在等待。实际发生的是只有一个线程被唤醒,其余的什么都不做。为什么显示只有一个线程被唤醒,如何解决?

class Plane extends Thread
{
    Runway runway;

    Plane(int id, Runway runway)
    {
        super(id + "");
        this.runway = runway;

    }

    public void run()
    {
        runway.taxi();
        runway.takeoff();
    }
}

class Runway
{
    boolean isFull;

    Runway()
    {
        isFull = false;;
    }

    public synchronized void taxi()
    {
        System.out.println(Thread.currentThread().getName() + " started to taxi");
        while(isFull)
        {
            System.out.println(Thread.currentThread().getName() + " is queued");
            try
            {
                wait();
            }
            catch(InterruptedException e){}     
        }
        isFull = true;
        System.out.println(Thread.currentThread().getName() + " entering runway");
    }

    public synchronized void takeoff()
    {
        try
        {
            Thread.currentThread().sleep(1000);
        }
        catch(InterruptedException e){}
        System.out.println(Thread.currentThread().getName() + " took off");
        isFull = false;
        notifyAll();
    }

    public static void main(String[] args)
    {
        Runway runway = new Runway();
        new Plane(1, runway).start();
        new Plane(2, runway).start();
        new Plane(3, runway).start();
        new Plane(4, runway).start();
    }
}

感谢您花时间帮助我:)

因为 notifyAll() 不是 wakeAll()。所有线程都会收到通知,但只有一个线程掌握了密钥并且正在 运行。所有其他人再次等待拉动。

这就是它的作用。它 "notifies" 所有等待的线程,但只有一个唤醒并获得 CPU。 notify() picks a waiting thread based on what the underlying thread implementation selects. notifyAll() 给所有等待线程一个平等的竞争机会。但无论哪种方式,只有一个线程获取上下文。

假设你有 4 个飞机,它们都是 start() 一个接一个地编辑的。

所有 4 个人都将尝试呼叫 taxi(),然后是 takeoff()

第一个会调用 taxi():

  • 获取锁,
  • 发现isFullfalse
  • isFull设置为true
  • return,释放锁

然后一个(或多个)剩余线程可能会调用 taxi()。如果他们这样做,他们:

  • 获取锁
  • 发现isFullfalse
  • 调用wait()释放锁

  • 尝试获取锁时阻塞

与此同时,return从 taxi() 编辑的线程将调用 takeoff()。这将:

  • 获取锁
  • 睡眠1秒,
  • 通知任何正在等待的线程
  • return,释放锁

那么这如何解释您所看到的?

假设当第一个线程从 taxi() 中 return 时,它立即能够重新获取锁并启动 takeoff() 调用。然后它会在持有锁的同时调用 sleep()。这将阻止任何其他线程开始它们的 taxi() 调用(如果它们还没有这样做的话)。然后在睡眠之后,它会调用notifyAll()。但这只会通知已进入 taxi() 调用和已调用 wait() 的线程。在启动 taxi() 调用时被阻塞的任何线程将永远不会看到通知。

(通知永远不会为不在 wait() 调用中的线程排队。)

这可能吗?嗯,是的。

启动线程是一个相对昂贵/耗时的过程,第一个启动的线程很可能会在下一个线程启动之前完成大量工作。有可能它会在第二个尝试调用 taxi().

之前一直到达 sleep 调用

其余线程可能会重复相同的模式。当进入 taxi() 的每个线程都可能释放并在另一个线程被调度之前重新获取它。 (线程调度由OS处理,它是为了效率而不是公平进行优化。如果要公平调度,则需要使用Lock对象。)


... how can a fix it?

更改您的密码,这样您就不会 sleep 持有锁。例如:

public void takeoff() {
    try {
        Thread.currentThread().sleep(1000);
    } catch (InterruptedException e) {
        // squash ...
    }
    System.out.println(Thread.currentThread().getName() + " took off");
    synchronize (this) {
        isFull = false;
        notifyAll();
    }
}