【问题标题】:Starvation-free solution of classic problem Single Lane Bridge Problem经典问题单车道桥问题的无饥饿解决方案
【发布时间】:2019-12-19 17:52:11
【问题描述】:

我正在尝试为经典的单车道桥梁问题提供解决方案,其中单车道桥梁连接两个村庄。它只使用一个线程,因此如果农民同时跳到桥的任一侧,可能会陷入僵局。到目前为止,这是我的解决方案,但我不确定如何让它不饿死?

public class SingleLaneBridge {

  public static void main(String[] args)
  {
    final Bridge bridge = new Bridge();

    Thread thNorthbound = new Thread( new Runnable() {

      @Override
      public void run() {

        while(true) {
          Farmer farmer = new Farmer(bridge);
          Thread th = new Thread(farmer);
          farmer.setName("North Farmer : "+ th.getId());
          th.start();
          try {
            TimeUnit.SECONDS.sleep((long)(Math.random()*10));
          } catch(InterruptedException iex) {
            iex.printStackTrace();
          }
        }

      }
    });

    Thread thSouthbound = new Thread( new Runnable() {

      @Override
      public void run() {

        while(true) {
          Farmer farmer = new Farmer(bridge);
          Thread th = new Thread(farmer);
          farmer.setName("South Farmer : "+th.getId());
          th.start();
          try {
            TimeUnit.SECONDS.sleep((long)(Math.random()*10));
          }
          catch(InterruptedException iex)
          {
            iex.printStackTrace();
          }
        }
      }
    });

    thNorthbound.start();
    thSouthbound.start();
  }

}

class Bridge {
  private final Semaphore semaphore;

  public Bridge() {
    semaphore = new Semaphore(1);
  }
  public void crossBridge(Farmer farmer) {
    try {
      System.out.printf("Farmer trying to cross the bridge.\n",farmer.getName());
      semaphore.acquire();
      System.out.printf("Farmer crossing the bridge.\n",farmer.getName());
      long duration = (long)(Math.random() * 10);
      TimeUnit.SECONDS.sleep(duration);
    }
    catch(InterruptedException iex) {
      iex.printStackTrace();
    }
    finally {
      System.out.printf("Farmer as crossed the bridge.\n",farmer.getName());
      semaphore.release();
    }
  }
}

class Farmer implements Runnable
{
  private String name;
  private Bridge bridge;

  public Farmer(Bridge bridge)
  {
    this.bridge = bridge;
  }

  public void run() {
    bridge.crossBridge(this);
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

【问题讨论】:

  • 你的代码有很多问题,但死锁的风险不是其中之一。
  • 至于确保不饿死——你读过Semaphore的Javadoc吗?有一个你没有使用的构造函数。 . .
  • @ruakh 是的,我阅读了 Javadoc,您指的是什么?
  • 我的代码有什么问题?
  • 这对于评论来说太大了,所以我已经在答案中发布了它。 (抱歉耽搁了。)

标签: java deadlock semaphore java-threads starvation


【解决方案1】:

首先,免责声明:您的问题的某些部分没有真正的意义,但我认为这可能更多是由于滥用术语而不是误解本身。不幸的是,如果您想清楚地传达您的问题并理解您所阅读的内容,那么术语实际上非常重要。我在下面已经尽力了,但很可能我误解了你的真正问题。


它只使用一个线程 […]

我不确定你的意思;它实际上为每个农民启动了一个单独的线程。

如果农民同时跳到桥的两侧,它 [...] 可能会陷入僵局。

这是不正确的;这里唯一的锁定机制是单个信号量,并且任何获得许可的线程都保证在再次尝试获得许可之前释放它,因此不可能出现deadlock


我发现您的代码存在三个主要问题:

  1. 您的介绍表明它正在尝试对单车道桥梁进行建模;但它并没有真正做到这一点,至少我理解“单车道桥”的概念。具体来说:
    • 您的代码在任何给定时间只允许一个农民使用这座桥;但是,如果有很多农民都想从同一个方向过桥,那么“单车道桥”就可以了。 (“单车道桥”应该只防止两个农民在相反方向过马路。)
      • 当您说您的解决方案“仅使用一个线程”时,这个问题是您的意思吗?
    • 如果农民 FG 都朝着同一个方向,农民 F 在农民 G 之前到达,我认为“单车道桥”永远不会让农民 G 在农民 F 之前通过。但是,您的代码不提供该保证。 (注意,此保证仅适用于朝相同方向的农民。如果农民朝相反方向前进,则获得农民G可能是有效的> 如果已经有另一个农民与农民G朝着相同的方向前进,那就先走。这是你的设计决定。)
  2. 您的代码在try-block 中调用acquire,然后在相应的finally-block 中调用release。这是不安全的,因为这意味着即使该线程从未成功获得许可,线程也可能释放许可。 (因此,例如,如果线程 T 持有许可并且线程 UV 都在等待获取许可,则调用线程 U 上的 Thread.interrupt 将导致线程 U 抛出 InterruptedException 并释放线程 T 的许可,从而让线程 V立即获得许可证!)
  3. 平均而言,在一个农民使用这座桥的时间里,会出现两个新的农民,每个方向都有一个。由于(如上所述)您一次只允许一个农民使用这座桥,这意味着您将越来越落后,越来越多的农民在等待使用这座桥。
    • 当您说您的解决方案不是“无饥饿”时,您指的是这个问题吗?

但要回答您的具体问题:

[...] 我不知道如何让它不饿死?

如果您写new Semaphore(1, true) 而不仅仅是new Semaphore(1)(即,如果您将信号量的“公平参数”设置为true),那么您的信号量将确保农民可以按照到达的顺序使用网桥.这将保证没有农民线程会饿死,因为每个农民都会保证在前一个农民释放许可证后立即获得许可证。

。 . .但即使有了这个公平参数,你仍然会遇到(上面提到的)农民不断出现的问题,而你无法通过它们。这可能会让您看起来像是遇到了resource starvation 问题,但实际上问题在于您没有足够的资源来尝试使用它。

要解决此问题,您确实需要解决上述问题 #1,并允许多个农民同时过桥,只要他们朝同一个方向行驶。我不认为像java.util.concurrent.Semaphore 这样的计数信号量会减少它。如果您需要确保在任何给定时间最多获得 n 个许可,则该类很有用,但在您的情况下,只要所有持有者都可以获取许可的数量没有限制朝着同一个方向前进。

相反,我认为您会想要以下内容:

  • AtomicInteger 或类似名称,用于跟踪目前有多少农民获准穿越。
  • 一个线程安全 List 的农民等待北行,一个农民等待南行。
  • Farmer 有一个布尔标志来指示它是否有权使用网桥。
  • 当农民出现时,如果桥已经在使用中,他们会将自己添加到相应的列表中,输入synchronized/wait 循环,直到他们的标志设置为true
  • 当农民用完桥后,他们会更新AtomicInteger,如果现在为零,他们会检查任何等待的农民并唤醒他们。

【讨论】:

  • 谢谢。很抱歉造成误解,是的,代码应该通过使用 Java 信号量来防止死锁,所以你基本上确认了这个问题是正确的。我试图做的是更新问题以使其免于饥饿。
猜你喜欢
  • 1970-01-01
  • 2023-01-07
  • 1970-01-01
  • 2012-10-11
  • 2014-07-07
  • 2019-02-07
  • 1970-01-01
  • 1970-01-01
  • 2020-02-19
相关资源
最近更新 更多