简体   繁体   English

防止并发方法执行

[英]Prevent concurrent method execution

I have a class which has a start() and stop() method and want to do the following:我有一个具有start()stop()方法的类,并希望执行以下操作:

1 One of the methods must not be called while the other one is running, eg when start() is called then stop() must not be called at the same time. 1 不得在另一个方法运行时调用其中一个方法,例如,在调用start()时,不得同时调用stop()
2a A call to either of the methods while it is already running should either be skipped immediately , 2a 应立即跳过对已运行的任一方法的调用,
2b or they should return the same time the initial call (which owns the lock) returns. 2b 或者他们应该在初始调用(拥有锁)返回的同时返回。

I thought that the first requirement could be achieved by adding a synchronized block.我认为第一个要求可以通过添加一个synchronized块来实现。 The requirement 2a is probably a good fit for an AtomicBoolean .要求 2a 可能非常适合AtomicBoolean However, to me this looks more complicated than it should be.然而,对我来说,这看起来比它应该的更复杂。 Are there other possibilities?还有其他可能吗? Is there maybe a class that fulfills both requirements in one go?是否有一个课程可以一次性满足这两个要求? Don't really know how 2b could be achieved.真的不知道如何实现2b。

When I just re-read my example code I realized that the synchronized probably needs to be before the isStarted check.当我重新阅读我的例子代码,我意识到, synchronized可能需要是前isStarted检查。 However, then I could not return immediately when a call is already inside the method.但是,当调用已经在方法内部时,我无法立即返回。 On the other hand, with the code given as below, I cannot prevent that one thread compares and sets isStarted to true and then gets suspended and another thread in stop() does the stop logic although the start() logic has not been completed.另一方面,使用下面给出的代码,我无法阻止一个线程比较并将isStarted设置为true然后挂起,而stop()中的另一个线程执行停止逻辑,尽管start()逻辑尚未完成。

Example code示例代码


    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()");
        }        
    }

Let's imagine this scenario:让我们想象一下这个场景:

  1. Thread A enters 'start'.线程 A 进入“开始”。
  2. Thread A is busy, trying to dig through the 'start' logic.线程 A 很忙,试图挖掘“开始”逻辑。 It is taking a while.这需要一段时间。
  3. Whilst A is still busy, thus the status of your code is 'starting in progress', but not yet 'I have started', thread B too calls start.虽然 A 仍然很忙,因此您的代码的状态是“正在开始”,但还不是“我已经开始”,线程 B 也调用了开始。

What you said is that you're okay with B returning immediately.你说的是B马上回来你没问题。

I question whether you really mean that;我怀疑你是否真的是这个意思; that would mean that code in B invokes 'start', and then this invocation returns, but the object isn't in the 'started' state yet.这意味着 B 中的代码调用“start”,然后该调用返回,但对象尚未处于“started”状态。 Surely you intend for a call to start() to return normally only once the object has actually been placed in the 'running' state, and return via exception if that is not possible.当然,您打算调用 start() 只有在对象实际处于“正在运行”状态时才能正常返回,如果不可能,则通过异常返回。

Given that I interpreted your needs correctly, forget the boolean;鉴于我正确解释了您的需求,请忘记布尔值; all you need is synchronized.您所需要的只是同步。 Remember, locks are part of your API, and we don't have public fields as a rule in java, so you shouldn't have public locks unless you document extensively (and consider the way your code interacts with the lock as part of the public API that you cannot modify without breaking backwards compatibility - not usually a promise you want to hand out casually, as that's quite a pair of handcuffs for future updates), so, your idea to make a private lock field is great.请记住,锁是您的 API 的一部分,我们在 Java 中没有公共字段作为规则,因此您不应该拥有公共锁,除非您有大量文档(并考虑您的代码与锁交互的方式作为在不破坏向后兼容性的情况下您无法修改的公共 API - 通常不是您想随便分发的承诺,因为这是未来更新的一副手铐),因此,您制作私有锁字段的想法很棒。 However, we already have a private unique instance, which we can re-use for the locks:但是,我们已经有一个私有的唯一实例,我们可以将其重用于锁:

    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() { ... }
}

This code will ensure that invoking 'start();'此代码将确保调用 'start();' will guarantee that the code was in 'started' state when it returns normally, or at least, it was (the one exception is if some other thread was waiting to stop it and did so immediately after the thread was started; presumably, as weird as that is, whichever state of affairs caused stop() to run wanted this to happen).将保证代码在正常返回时处于“已启动”状态,或者至少是(一个例外是,如果某个其他线程正在等待停止它并在线程启动后立即停止;大概,很奇怪也就是说,无论哪种情况导致 stop() 运行都希望发生这种情况)。

Furthermore, if you want the locking behaviour to be part of the API of this class, you can do so.此外,如果您希望锁定行为成为此类 API 的一部分,您可以这样做。 For example, you could add:例如,您可以添加:

/** 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();
    }
}

Your code can implement a kind of double checking like this:您的代码可以实现一种像这样的双重检查:

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...
    }
}

This should match all of your conditions 1, 2a, 2b.这应该符合您的所有条件 1、2a、2b。 If you expose isStarted, you could modify the flag AFTER, not BEFORE the action (do start/do stop) finished, but in this case 2b will happen a bit frequently.如果您公开 isStarted,您可以在操作(开始/停止)完成之后修改标志,但在这种情况下 2b 会频繁发生。

But, in the real life, I'd prefer to use messaging between threads rather than locking which can lead to deadlocks easily (for example, you notify some listeners under the lock and the listeners use their own locks).但是,在现实生活中,我更喜欢在线程之间使用消息传递而不是锁定,这很容易导致死锁(例如,您通知锁定下的某些侦听器,而侦听器使用自己的锁)。

A simple BlockingQueue may be such messaging queue.一个简单的 BlockingQueue 可能就是这样的消息队列。 One single thread consumes all the commands from the queue and executes them one by one (in its run() method) according to its current state which isn't a shared one now.一个线程消耗队列中的所有命令,并根据其当前状态(现在不是共享状态)一一(在其 run() 方法中)执行它们。 Another threads put Start/Stop commands to the queue.另一个线程将启动/停止命令放入队列。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM