Ton*_*ark 0 java concurrency lifecycle atomic-values
我需要确保特定的启动和停止代码在每个实例生命周期只执行一次,并且实例不能“重新启动”。以下代码是否适用于多个线程可能作用于实例的场景?
public final class MyRunnable {
private final AtomicBoolean active = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
public void start() {
if (closed.get()) {
throw new IllegalStateException("Already closed!");
}
if (active.get()) {
throw new IllegalStateException("Already running!");
}
active.set(true);
// My one-time start code.
// My runnable code.
}
public void stop() {
if (closed.get()) {
throw new IllegalStateException("Already stopped!");
}
if (!active.get()) {
throw new IllegalStateException("Stopping or already stopped!");
}
active.set(false);
// My one-time stop code.
closed.set(true);
}
}
Run Code Online (Sandbox Code Playgroud)
出于两个原因,我会选择一个 3 值状态。
首先,在active,closed“元组”的 4 个可能值中只有 3 个有意义,将两者都设置为true导致(可能是良性的,但仍然是)无效状态。您可能会认为它纯粹是迂腐,但清晰的设计通常会带来其他好处。
这将我们巧妙地引向了第二个更可怕的原因:
active.set(false);
// <-- what if someone calls start() here?
closed.set(true); //I assume you wanted to set it to true
Run Code Online (Sandbox Code Playgroud)
正如你可以从我的评论看,你已经有了一个薄弱环节在那里,有人可以想见,叫start()你设置后active到false,但你之前设置closed到true。
现在您可能只是说“好吧,让我们交换两个然后closed先设置”,但是您必须解释为什么这两个绝对不会被 JVM 重新排序。并且您最终可能会将两个标志都设置为true,从而导致上面概述的“无效状态”。
这里还有另一个单独的问题:您遵循的模式是调用get()以检查值,然后再检查其他值set()。正如PetrosP 指出的那样,这不是一个原子操作,你可以调用start()1000 次,所有这些都被active视为false. 您需要compareAndSet改用它,它是原子的(这是类的全部意义Atomic*),从而保证只有一个线程可以推进状态标志。
因此,让我们将两者结合起来,使用单个 3 值状态(AtomicInteger为了简单起见,我使用 过,但您可以使用AtomicReference和 true enum)和compareAndSet():
public final class MyRunnable {
private static final int READY_TO_START = 0;
private static final int ACTIVE = 1;
private static final int STOPPED = 2;
private final AtomicInteger status = new AtomicInteger(READY_TO_START);
public void start() {
if (!status.compareAndSet(READY_TO_START, ACTIVE)) {
throw new IllegalStateException("Already started");
}
// My one-time start code.
}
public void stop() {
if (!status.compareAndSet(ACTIVE, STOPPED)) {
throw new IllegalStateException("Can't stop, either not started or already stopped");
}
// My one-time stop code.
}
}
Run Code Online (Sandbox Code Playgroud)