【问题标题】:Why is notifyAll() not waking all of the threads in this example?为什么 notifyAll() 在这个例子中没有唤醒所有线程?
【发布时间】:2016-04-03 01:14:54
【问题描述】:

我试图弄清楚如何使用等待和通知,所以我写了这个小例子,其中有几架飞机在起飞前等待跑道清理干净,我遇到的问题是,当飞机起飞并调用 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();
    }
}

感谢您花时间帮助我:)

【问题讨论】:

    标签: java multithreading wait notify


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      假设您有 4 架飞机,它们都是start()-ed 一个接一个。

      所有 4 人都将尝试调用 taxi(),然后是 takeoff()

      第一个会调用taxi():

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

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

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

      • 在尝试获取锁时阻塞

      与此同时,从taxi() 返回的线程将调用takeoff()。这将:

      • 获取锁
      • 睡一秒钟,
      • 通知所有正在等待的线程
      • 返回,释放锁。

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

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

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

      这可能吗?嗯,是的。

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

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


      ...如何解决?

      更改您的密码,以便您在持有锁时不要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();
          }
      }
      

      【讨论】:

        【解决方案3】:

        这就是它的作用。它“通知”所有等待的线程,但只有一个唤醒并获得 CPU。 notify() 根据底层线程实现选择的内容选择等待线程。 notifyAll() 为所有等待的线程提供了平等的竞争机会。但无论哪种方式,只有一个线程获取上下文。

        【讨论】:

        • 其实OP的代码是这样写的,其他线程并没有等待,所以从来没有看到通知。有关详细分析(和修复),请参阅我的答案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-07-16
        • 1970-01-01
        • 1970-01-01
        • 2020-11-09
        • 2011-03-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多