防止并发方法执行

oiopk7p5  于 2021-07-08  发布在  Java
关注(0)|答案(2)|浏览(377)

我有一个班级 start() 以及 stop() 方法并希望执行以下操作:
1其中一个方法不能在另一个正在运行时调用,例如 start() 那就叫什么 stop() 不能同时调用。
2a调用已经运行的方法时,应该立即跳过,
2b或者它们应该在初始调用(拥有锁)返回的同时返回。
我认为第一个要求可以通过添加 synchronized 阻止。要求2a可能很适合 AtomicBoolean . 然而,对我来说,这看起来比它应该是复杂的。还有其他可能性吗?是否有一个类可以一次性满足这两个要求?我真的不知道2b是怎么实现的。
当我重新阅读示例代码时,我意识到 synchronized 可能需要在 isStarted 检查。但是,当调用已经在方法中时,我无法立即返回。另一方面,对于下面给出的代码,我不能阻止一个线程进行比较和设置 isStartedtrue 然后挂起另一个线程 stop() 尽管 start() 逻辑尚未完成。

示例代码

private final AtomicBoolean isStarted = new AtomicBoolean(false);
    private final Object lock = new Object();

    public void start() {
        if (isStarted.compareAndSet(false, true)) {
            synchronized(lock) {
                // start logic
            }
        } else {
            log.warn("Ignored attempt to start()");
        }
    }

    public void stop() {
        if (isStarted.compareAndSet(true, false)) {
            synchronized(lock) {
                // stop logic
            }
        } else {
            log.warn("Ignored attempt to stop()");
        }        
    }
mbskvtky

mbskvtky1#

您的代码可以实现如下的双重检查:

private final Object lock = new Object();
private volatile boolean isStarted = false;

public void start() {
    if (isStarted) {
        return;
    }
    synchronized(lock) {
        if (isStarted) {
            return;
        }
        isStarted = true;

        // ...do start...
    }
}

public void stop() {
    if (!isStarted) {
        return;
    }
    synchronized(lock) {
        if (!isStarted) {
            return;
        }
        isStarted = false;

        // ...do stop...
    }
}

这应该符合你的所有条件1,2a,2b。如果您公开isstarted,您可以在操作(do start/do stop)完成之后而不是之前修改标志,但是在这种情况下2b会频繁发生。
但是,在现实生活中,我更喜欢在线程之间使用消息传递,而不是使用容易导致死锁的锁(例如,您在锁下通知一些侦听器,侦听器使用自己的锁)。
简单的阻塞队列可以是这样的消息传递队列。一个线程使用队列中的所有命令,并根据其当前状态(现在不是共享状态)逐个执行它们(在其run()方法中)。另一个线程将启动/停止命令放入队列。

y1aodyip

y1aodyip2#

让我们想象一下这个场景:
线程a进入“start”。
线程a正忙着,试图挖掘“开始”逻辑。这需要一段时间。
当a仍然忙时,因此代码的状态是“starting in progress”,而不是“i have start”,线程b也会调用start。
你说b马上回来你没问题。
我怀疑你是不是真的那么想;这意味着b中的代码调用“start”,然后这个调用返回,但是对象还没有处于“started”状态。当然,只有当对象实际处于“running”状态时,才能正常返回对start()的调用,如果不可能,则通过exception返回。
既然我正确地解释了你的需求,那就忘掉布尔值吧;你所需要的就是同步。记住,锁是api的一部分,java中没有公共字段,所以你不应该拥有公共锁,除非你做了大量的文档记录(并且考虑到你的代码与锁交互的方式是公共api的一部分,你不能在不破坏向后兼容性的情况下修改它-通常你不想随便地给出一个承诺,因为这对将来的更新来说是一副手铐),所以,你的想法,使私人锁领域是伟大的。但是,我们已经有了一个私有的唯一示例,我们可以将其重新用于锁:

private final AtomicBoolean isStarted = new AtomicBoolean(false);

    public void start() {
        synchronized (isStarted) {
            if (isStarted.compareAndSet(false, true)) startLogic();
        }
    }

    public void stop() {
        synchronized (isStarted) {
            if (isStarted.compareAndSet(true, false)) stopLogic();
        }
    }

    public boolean isStarted() {
        return isStarted.get();
    }

    private void startLogic() { ... }
    private void stopLogic() { ... }
}

此代码将确保调用'start();'将保证代码在正常返回时处于“started”状态,或者至少是处于“started”状态(一个例外是如果其他线程正在等待停止它,并且在线程启动后立即停止;想必,奇怪的是,不管是什么状态导致stop()运行,都希望发生这种情况)。
此外,如果希望锁定行为成为此类api的一部分,可以这样做。例如,您可以添加:

/**Runs the provided runnable such that the service is running throughout.
 * Will start the service if neccessary and will block attempts to stop
 * the service whilst running. Restores the state afterwards.
 */
public void run(Runnable r) {
    synchronized (isStarted) {
        boolean targetState = isStarted.get();
        start(); // this is no problem; locks are re-entrant in java.
        r.run();
        if (!targetState) stop();
    }
}

相关问题