【问题标题】:Synchronisation using Dekker's algorithm使用 Dekker 算法进行同步
【发布时间】:2021-01-11 17:12:11
【问题描述】:

我希望能够更改 runTrain() 方法以允许同步以避免两列火车同时通过。

秘鲁公共类扩展铁路{

public Peru() throws SetUpError {
    super("Peru",new Delay(0.1,0.3));
}

/**
 * Run the train on the railway.
 * This method currently does not provide any synchronisation to avoid two 
 * trains being in the pass at the same time.
 */
public void runTrain() throws RailwaySystemError {
    Clock clock = getRailwaySystem().getClock();
    Railway nextRailway = getRailwaySystem().getNextRailway(this);
    while (!clock.timeOut()) {
        choochoo();
        getBasket().putStone(this);
        while (nextRailway.getBasket().hasStone(this)) {
            getBasket().takeStone(this);
            siesta();
            getBasket().putStone(this);
        }
        crossPass();
        getBasket().takeStone(this);
    }
}

}

目前的问题是我当前的方法不允许同步让两列火车同时通过。这两列火车是秘鲁和玻利维亚。

玻利维亚公共类扩展铁路{

public Bolivia() throws SetUpError {
    super("Bolivia",new Delay(0.1,0.3));
}

/**
 * Run the train on the railway.
 * This method currently does not provide any synchronisation to avoid two 
 * trains being in the pass at the same time.
 */
public void runTrain() throws RailwaySystemError {
    Clock clock = getRailwaySystem().getClock();
    Railway nextRailway = getRailwaySystem().getNextRailway(this);
    while (!clock.timeOut()) {
        choochoo();
        getBasket().putStone(this);
        while (nextRailway.getBasket().hasStone(this)) {
            getBasket().takeStone(this);
            siesta();
            getBasket().putStone(this);
        }
        crossPass();
        getBasket().takeStone(this);
    }
}

}

下面是抽象类,所有方法都是从扩展线程中定义的。

公共抽象类铁路扩展线程{ 私有字符串名称; // 铁路名称 私有静态篮子 sharedBasket = new Basket("共享篮子"); // 一个共享 //通知篮 私人篮筐; // 私人篮子 私人铁路系统铁路系统; //这条铁路构成系统的一部分 私人延迟延迟; // 这条铁路使用的延迟

private Position position; // the position of the train on this railway

public Railway(String name,Delay delay) {
    this.name = name;
    this.delay = delay;
    position = Position.END_PASS; // all trains start just after the
    basket = new Basket(name + "'s basket");
}

/**
 * Register this railway with a railway system
 * @param railwaySystem the railway system this railway must be registered with
 */
public void register(RailwaySystem railwaySystem) {
    this.railwaySystem = railwaySystem;
}

/**
 * Get the railway system this railway is registered with
 * @return the railway system this railway is registered with
 * @throws ProgrammingError if the railway is not registered
 */
public RailwaySystem getRailwaySystem() throws ProgrammingError {
    if (railwaySystem == null) {
        throw new ProgrammingError(name + " is not registered with a railway 
        system");
    }
    return railwaySystem;
}

/**
 * Get this railway's name
 * @return this railway's name
 */
public String name() {
    return name;
}

/**
 * Get this railway's private basket.
 * @return this railway's private basket
 */
public Basket getBasket() {
    return basket;
}

/**
 * Get the shared basket.
 * @return the basket shared between all railways.
 */
public static Basket getSharedBasket() {
    return sharedBasket;
}

/**
 * Use delay to generate a delay for this railway
 */
public void delay() {
    delay.delay();
}

// Fields keeping track of trains in the pass
private static int trainsInPass = 0; // how many trains are in the pass

/**
 * Defines parts of the railway system. These are specified as:
 * <ul>
 *     <li> START_PASS: just before entering the shared pass.</li>
 *     <li> IN_PASS: in the shared pass.</li>
 *     <li> END_PASS: at the end of the shared pass.</li>
 * </ul>
 * Trains start at the end of the pass, and must thereafter cycle through 
 * positions START_PASS, IN_PASS, END_PASS.
 */
public static enum Position {
    START_PASS, IN_PASS, END_PASS;
    
    public String toString() {
        switch (this) {
        case START_PASS: return "at the start of the pass";
        case IN_PASS: return "in the pass";
        case END_PASS: return "at the end of the pass";
        default: return "at an undefined position on the railway (ERROR)";
        }
    }
}
/**
 * Enter the pass.
 * This method does <i>not</i> check if it is safe to enter the pass. It is 
 * merely for
 * administration of the information about trains in the pass.
 * @throws ProgrammingError if this railway thinks it already has a train in 
 * the pass.
 */
private synchronized void enterPass() throws ProgrammingError {
    railwaySystem.trace(name + ": entering pass");
    if (position != Position.START_PASS) {
        throw new ProgrammingError(name + " cannot enter the pass, it is not " 
        + Position.START_PASS + ", it is " + position + ".");
    }
    position = Position.IN_PASS;
    trainsInPass++;
}

/**
 * Leave the pass.
 * This method is merely for administration of the information about trains in 
 * the pass.
 * @throws ProgrammingError if this railway thinks it does not have a train in 
 * the pass,
 *                          or if there is no record of any trains in the pass.
 */
private synchronized void leavePass() throws ProgrammingError {
    if (position != Position.IN_PASS) {
        throw new ProgrammingError(name + " cannot leave the pass, it is not " 
        + Position.IN_PASS + ", it is " + position + ".");
    }
    if (trainsInPass == 0) {
        throw new ProgrammingError("There is no train to leave the pass (even 
        though " + name + " thinks it is in the pass.");
    }
    position = Position.END_PASS;
    trainsInPass--;
    railwaySystem.trace(name + ": leaving pass");
}

/**
 * Travel round the safe part of the railway (outside the pass).
 * @throws ProgrammingError if the train is not currently at the end of the 
 * pass (and therefore at the start of the
 *         safe part of the railway).
 */
public void choochoo() throws ProgrammingError {
    if (position != Position.END_PASS) {
        throw new ProgrammingError(name + " cannot traverse safe section, it is 
        not " + Position.END_PASS + ", it is " + position + ".");
    }
    railwaySystem.trace (name + ": choo-choo");
    delay();
    position = Position.START_PASS;
}

/**
 * Have a siesta.
 */
public void siesta() {
    railwaySystem.trace(name + ": zzzzz");
    delay();
}

/**
 * Cross the pass.
 * @throws SafetyViolationError if there is/are already train(s) on the pass.
 */
public void crossPass() throws RailwaySystemError {
    enterPass();
    if (trainsInPass > 1) {
        throw new SafetyViolationError("There are now " + trainsInPass + " 
        trains in the pass!");
    }
    railwaySystem.trace(name + ": crossing pass");
    delay();
    leavePass();
}

// Error flag must be shared so that we can stop all railways if something goes 
// wrong    

private static boolean errorFlag = false;
protected static String errorMessage = "";

/**
 * Run the railway.
 */
public void run() {
    setErrorFlag(false);
    try {
        runTrain();
    } catch (RailwaySystemError error) {
        setErrorFlag(true);
        errorMessage = error.getMessage();
        System.out.println("!!! Something went wrong with the railway.\n\t" + 
        errorMessage);
    }
    if (errorOccurred()) {
        System.out.println("!!! " + name() + " shut down because of an 
        error.\n\t" + errorMessage);
    } else {
        System.out.println(name() + " shut down because time limit was 
        reached.");
    }
}

/**
 * Each railway should independently define how the trains are to be run, using 
 * the basket(s).
 * to maintain safety on the pass.
 * @throws RailwaySystemError if a safety violation occurs while the railway is 
 * being run.
 */
public abstract void runTrain() throws RailwaySystemError, RailwaySystemError;

/**
 * Set the shared error flag (if an error occurs).
 * @param errorFlag is true iff an error has occured.
 */
public static void setErrorFlag(boolean errorFlag) {
    Railway.errorFlag = errorFlag;
}

/**
 * Check the current error status.
 * @return true iff an error is currently active.
 */
public static boolean errorOccurred() {
    return errorFlag;
}

}

我终于得到了包含 main 方法的类。

公共类铁路系统{

private Clock clock = null; // the clock used to time railways - must be initialised for 
// each run
private List<Railway> railways;

public RailwaySystem(List<Railway> railways,Clock clock) {
    this.clock = clock;
    this.railways = railways;
    clock.register(this);
    for (Railway railway: railways) {
        railway.register(this);
    }
}

/**
 * Start the railway system
 */
private void start() {
    clock.start();
    for (Railway railway: railways) {
        railway.start();
    }
}

/**
 * Wait for the system to stop
 */
private void stop() throws RailwaySystemError {
    try {
        clock.join();
        for (Railway railway: railways) {
            railway.join();
        }
    } catch (InterruptedException interrupt) {
        throw new RailwaySystemError("The railway system was interrupted: " + 
        interrupt.getMessage());
    }
}

/**
 * Given a railway, get the next one in the system's list
 * @param railway the given railway
 * @return the next railway in the list 
 * @throws ProgrammingError if railway is neither peru not bolivia
 */
public Railway getNextRailway(Railway railway) throws ProgrammingError {
    int index = railways.indexOf(railway);
    if (index == -1) { // railway is not in the list
        throw new ProgrammingError(railway.name() + " is not registered with this system");
    }
    return railways.get((index+1) % railways.size());
}

/**
 * Get this system's clock
 * @return the system's clock
 * @throws SetUpError if the clock is not initialised
 */
public Clock getClock() throws SetUpError {
    if (clock == null) {
        throw new SetUpError("Clock has not been intialised");
    }
    return clock;
}

/**
 * Provide a facility for switching tracing on/off.
 **/
private boolean tracingOn = false;

/**
 * Switch tracing on.
 **/
public void traceOn() {
    tracingOn = true;
}

/**
 * Switch tracing off.
 **/
public void traceOff() {
    tracingOn = false;
}

/**
 * Trace, if tracing is on
 * @param trace the trace to be output
 */
public synchronized void trace(String trace) {
    if (tracingOn) {
        System.out.println(trace);
    }
}

public static void main(String[] args) throws RailwaySystemError {
    List<Railway> railways = new ArrayList<Railway>();
    railways.add(new Peru());
    railways.add(new Bolivia());
    Clock clock = new Clock(1.0,20); // 20 ticks of 1 second
    RailwaySystem system = new RailwaySystem(railways,clock);
    system.traceOn();
    system.start();
    system.stop();
}

}

我需要在我的 runTrain() 方法中允许同步,这是我目前遇到的问题。非常感谢

【问题讨论】:

    标签: java synchronization


    【解决方案1】:

    您正在处理的代码有问题。那...不是一个好兆头,您的教授/这本书不了解线程,但他们正在尝试教您。

    public void traceOff() {
        tracingOn = false;
    }
    
    public synchronized void trace(String trace) {
        if (tracingOn) {
            System.out.println(trace);
        }
    }
    

    每个线程都有一个邪恶的硬币。任何时候任何线程访问任何变量(用于读取或写入),该线程都会翻转这个硬币。头,它将使用自己的本地副本。即使只有一个字段,每个线程都有它的缓存副本,并且在头部,线程只更新/查看其本地副本。 Tails,它使用/更新实际字段而不是副本。

    这枚硬币是邪恶的:它不是一枚“公平”的硬币,而且当您编写它并运行您的测试时,它很可能在今天每次都翻转。然后当重要客户进来并演示您的应用程序时,硬币一直在翻转。因为墨菲就是不喜欢你。

    解决方案是阻止 VM 掷硬币,或者确保您的代码不关心结果。

    确保 VM 不会翻转它的唯一方法是在设置字段的行和读取字段。

    换句话说,上述两个动作(traceOfftrace没有按预期工作并导致该邪恶硬币的翻转,除非建立这样的 CBCA 关系tracingOn = false 调用“出现在”if (tracingOn) 调用之前。这意味着:假设线程 A 将跟踪设置为 on,并且 3 小时后,线程 B 调用 trace,JVM 可以像tracing 一样运行,这将是一个合法的 VM。它也可以表现为tracing 已关闭,这也是合法的:JMM 故意不做任何保证。显然,这是一个有问题的应用程序。显然,重点应该是跟踪绝对永远在这里。不幸的是,这需要 CBCA 关系,而此代码没有建立这一点。那么,如何实现呢?

    synchronized 是这样做的一种方法,但这里没有正确完成。每当代码退出 synchronized (x) 块时,然后当其他代码稍后进入 synchronized (x) 块时(并且由于同步,这些事件不可能同时发生),线程 1 执行的所有代码都“先于”线程 2 的代码。

    这里没有发生这种情况:traceOff 方法根本不是 synchronized,因此不存在 CBCA 关系,因此 此代码已损坏


    private static Basket sharedBasket = new Basket("shared basket");

    这绝对应该是@​​987654336@。

    目前的问题是我目前的方法不允许让两列火车同时通过。

    这似乎不对。您的代码确实 try 来解决这个问题;这肯定就是把石头放在篮子里的所有内容:

    getBasket().putStone(this);
    while (nextRailway.getBasket().hasStone(this)) {
      getBasket().takeStone(this);
      siesta();
      getBasket().putStone(this);
     }
    

    这个代码没有意义 - 篮子里的一块石头表示通道中有另一列火车,所以当那里有一块石头时,你等待,但你拿走了石头!这意味着如果第三列火车出现,kaboom。不要碰石头。就这样吧。

    我假设那个篮子的代码是提供的(不是你写的),并且是线程安全的,但是我们已经确定你的教授/这本书似乎不知道如何编写线程安全代码,也就是说一个假设,正如维基人所说,得到一个[需要引用]。假设篮子代码是正确且线程安全的,那么您拥有所需的所有工具,但您没有以正确的方式使用它们。

    这个想法毫无疑问是,要越过传球,你必须在篮子里放一块石头,但前提是篮子里还没有石头,然后越过传球,然后把石头拿出来表明你是完成您的旅行。如果你不能在篮子里放一块石头(因为里面已经有一块石头),那就等一会儿再试。在你成功地在空篮子里放一块石头之前,不要进入通行证。

    这里有两个重要的认识:

    1. “检查石头是否在篮子里”和“把石头放在篮子里”的任务是相互交织的:不能顺序执行;如果两列火车都检查篮子,都看到篮子是空的,然后都放了一块石头,然后都进入了通道,然后都撞车并死于可怕的火中怎么办? “放入石头,但只有在没有石头的情况下,如果成功则报告”的概念是一个不能拆分的单个操作 - 这称为 原子 操作。李>
    2. 无论以何种方式或方式,无论火车如何将石块放入篮中,都需要确保它在完成后将石块取出。

    篮子需要支撑它,不清楚是否支撑。如果已经有石头,putStone 方法会失败吗?如果是这样,它是如何失败的?它是返回一个布尔值false,还是抛出一些东西?

    如果它没有失败(你可以把石头放在篮子里,即使它已经有石头了),这个方法几乎完全没用 - 你需要synchronize 来达到你的原子性要求.

    如果它确实失败了,那么就使用它。你的算法需要改成这样:

    while (true) { // keep trying until we succeed.
        boolean actuallyPlacedStone = basket.putStoneIfBasketEmpty(this);
        if (!actuallyPlacedStone) {
           // the basket is filled, we can't go.
           siesta(); // sleep a while, and ...
           continue; // start from the top
        }
    
        // if we got here, we placed the stone!
        try {
            crossThePass();
        } finally {
            // no matter how we get out of that pass, be it on rails
            // or by tumbling down the mountain in a horrible accident...
            // the pass is now clear, so, take the stone with you.
            basket.takeStone(this);
        }
        break; // we're through, no need to repeat this process.
    }
    

    如果篮子没有任何线程保证,我想我们可以将它用作锁。 所有与该篮子的交互必须现在都在一个同步块中,以确保没有 2 个线程同时作用于篮子,以及在周围建立 CBCA 并避免邪恶的硬币毁了我们的一天。我会制作辅助方法:

    public boolean placeStoneIfEmpty(Basket b) {
        synchronized (b) {
            if (b.hasStone()) return false;
            b.putStone(this);
            return true;
        }
    }
    
    public void removeStone(Basket b) {
        synchronized (b) {
            b.removeStone(this);
        }
    }
    

    【讨论】:

    • 非常感谢您的回复。需要进行同步以避免两列火车进入通道。那是我的错误,对不起。
    • a so-called 'comes before/comes after' (CBCA) relationship Java 语言规范没有这样称呼它。它使用 happens-before。 (对于导致这种情况的动作,它是 synchronizes-with。)你的术语对我来说很新颖,我从来没有听说过有人在15 年的 Java 编程经验。 (不过,“邪恶的硬币”是一个很好的类比。)
    • 是的,我明白了 - 这个答案涵盖了您需要的所有详细信息:[1] 您正在处理的代码至少可以说是可疑的,[2] 假设您需要做什么篮子有你需要的东西(具体来说,一个原子'放在篮子里,但只有当它是空的'),以及 [3] 如果篮子没有,你需要做什么。通过对该篮子进行适当的操作,如果已经有另一辆火车在其中,您可以轻松确保没有火车进入。
    • 我还需要算法在某个时候结束!!下面的代码也是需要包含的!时钟时钟 = getRailwaySystem().getClock();铁路 nextRailway = getRailwaySystem().getNextRailway(this);
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多