【问题标题】:How to synchronize shared variable with two Semaphores?如何将共享变量与两个信号量同步?
【发布时间】:2021-12-08 20:27:38
【问题描述】:

我有一个练习,我有一个盛宴,其中来自 N = 10 人的人一次从锅中吃 1 份。锅的最大份量 M = 5。还有一个厨师在锅空了的时候给锅填满。servingsAvailable = 0。在填满的过程中人不能吃东西。我必须同步线程,只从 Pot 类中更改方法 fill 和 getServings(这些方法一开始是空的)。

你能告诉我这段代码我做错了什么吗?总量应为 1000,但总是更少。我实现了锅装满然后 5 人吃,然后是装满等但吃的份数不一致的情况。

人物类

public class Person extends Thread { // Reprezentuje tubylca
    Pot pot;
    int servingsConsumed = 0;
    public Person(String name, Pot pot) {
        super(name);
        this.pot = pot;
    }
    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; ++i) {
                pot.getServing(this.getName());
                ++servingsConsumed;
                Thread.yield();
            }
        } catch(InterruptedException e) {
            return ;
        }
    }
}

烹饪课

public class Cook extends Thread { // Reprezentuje kucharza
    Pot pot;
    public Cook(Pot pot) {
        this.pot = pot;
        setDaemon(true);
    }
    @Override
    public void run() {
        try {
            while(!isInterrupted()) {
                pot.fill();
            }
        } catch(InterruptedException e) {
            return ;
        }
    }
}

锅类

import java.util.concurrent.Semaphore;

public class Pot {
    static final int M = 5; // Pojemność kotła
    private Semaphore emptyPot = new Semaphore(1);
    private Semaphore available = new Semaphore(0);
    private int servingsAvailable = 0;
    private int totalServedCount = 0;

    private synchronized void insertServings(int value) {
        servingsAvailable = value;
    }

    private synchronized int removeServing() {
        --servingsAvailable;
        ++totalServedCount;
        return servingsAvailable;
    }

    public int getTotalServedCount() {
        return totalServedCount;
    }

    public void getServing(String nameOfPerson) throws InterruptedException {
        available.acquire();
        if (servingsAvailable != 0) {
            removeServing();
            System.out.println(nameOfPerson + " ate 1 portion from pot");
        }
        available.release();
    }

    public void fill() throws InterruptedException {
        available.acquire();
        if (servingsAvailable == 0) {
            insertServings(M);
            System.out.println("Fill the pot with M = " + M);
        }
        available.release();
    }
}

宴会班(主)

public class Feast {
    public static void main(String[] args) throws InterruptedException {
        Pot pot = new Pot();
        Cook cook = new Cook(pot);
        final int N = 10;
        Person[] people = new Person[N];
        for (int i = 0; i < people.length; ++i) {
            people[i] = new Person("Person " + i, pot);
        }
        cook.start();
        for (Thread t : people) {
            t.start();
        }
        for (Thread t : people) {
            t.join();
        }
        cook.interrupt();
        System.out.printf("Total served: %d.\n", pot.getTotalServedCount());
        for (Person p : people) {
            System.out.printf("[%s] Ate %d servings.\n", p.getName(), p.servingsConsumed);
        }
        System.out.println("Finishing simulation.");
    }
}

到目前为止我达到的结果: 我认为这里应该显示 1000 而不是 245。

【问题讨论】:

  • totalServedCount 仅在 servingsAvailable != 0 时递增。
  • 但是servingsConsumed无论如何都会递增。
  • Pot 中,您的一些方法是synchronized,而有些则不是。这是行不通的,因为现在您使用两个不同的锁来保护相同的数据。
  • getTotalServedCount() 的情况下,您根本没有使用任何锁,这是错误的。 Java要求你保护读写,否则同步无效。
  • @shmosel 在 n - 1 加入后。 Cook 线程尚未加入。通常,将责任分散到多个类是一个糟糕的设计。它导致了我们在这里遇到的错误……以及大量其他错误。

标签: java concurrency


【解决方案1】:

您将信号量用作简单的互斥体,调用者无法知道有多少份可用。如果您想指示锅的状态,您应该在食物装满和食用时更新它们:

public void getServing(String nameOfPerson) throws InterruptedException {
    // take a permit and keep it
    available.acquire();
    System.out.println(nameOfPerson + " ate 1 portion from pot");
    if (removeServing() == 0) {
        // release a refill permit to the Cook
        emptyPot.release();
    }
}

public void fill() throws InterruptedException {
    // wait till pot is empty
    emptyPot.acquire();
    insertServings(M);
    System.out.println("Fill the pot with M = " + M);
    // release a permit for each serving
    available.release(M);
}

【讨论】:

  • You're using your semaphores like a simple mutex 这可能是练习的重点,尽管我同意这些说明看起来很奇怪。令我困惑的是synchronized 在某些方法上的使用,但不是全部。那里有一些明显违反 happens-before 的规定。
  • 这几乎是完美的,但有时 3,4,6,7 或更多的人在重新装满后从锅里吃东西,这有点奇怪,因为 M 等于 5。你知道导致这种情况的原因吗?
  • @Holger 为什么不呢?谁吃最后一份,谁就吃。
  • @MrFisherman 有一个竞争条件,在重新装满罐子并消耗新份量后,可以打印第一条消息。立即试用发布前的印刷品。
  • 好的,这就是我所缺少的……不明显,因为removeServing() 的代码需要滚动几页。
猜你喜欢
  • 2013-05-30
  • 1970-01-01
  • 1970-01-01
  • 2014-12-31
  • 2010-11-27
  • 2012-05-17
  • 2018-09-25
  • 1970-01-01
相关资源
最近更新 更多