使用 AtomicBoolean 控制实例的状态
Controlling an instance's state with AtomicBoolean
我需要确保每个实例生命周期只执行一次特定的启动和停止代码,并且实例不能 "restarted"。以下代码是否适用于多个线程可能作用于实例的场景?
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);
}
}
这个解决方案是不够的。考虑这个场景:两个线程同时进入 start()。调用 active.get()
并返回 false
。然后第二个调用 active.get()
并且它也得到 false
。在这种情况下,他们都将继续。然后第一个将 active 设置为 true。此时的第二个也将 active 设置为 true,它们都将继续到应该 运行 一次的其余代码。
解决方案可能是这样的:
public final class MyRunnable {
private final AtomicBoolean active = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
public void start() {
synchronized (this) {
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() {
synchronized (this) {
if (closed.get()) {
throw new IllegalStateException("Already stopped!");
}
if (!active.get()) {
throw new IllegalStateException("Stopping or already stopped!");
}
// My one-time stop code.
closed.set(false);
active.set(false);
}
}
}
出于两个原因,我会选择单一的 3 值状态。
首先,在 active,closed
"tuple" 的 4 个可能值中,只有 3 个有意义,将两者都设置为 true
会导致(可能是良性的,但仍然)无效状态.您可能会认为它纯粹是迂腐,但清晰的设计通常会带来其他好处。
这直接将我们引向了第二个更可怕的原因:
active.set(false);
// <-- what if someone calls start() here?
closed.set(true); //I assume you wanted to set it to true
正如你从我的评论中看到的那样,你在那里有一个弱点,有人可以想象在你将 active
设置为 false
之后但在你之前调用 start()
将 closed
设置为 true
.
现在你可以直接说"okay, let's swap the two then and set closed
first",但是接下来你要解释为什么这两个肯定不会被JVM重新排序。您最终可能会将两个标志都设置为 true
,从而导致上述 "invalid state"。
这里还有另一个单独的问题:您遵循的模式是调用 get()
来检查值,然后 set()
稍后再调用它。由于 ,这不是一个原子操作,您可以调用 start()
1000 次,所有这些都将 active
视为 false
。您需要使用 compareAndSet
代替,其中 是 原子的(这是 Atomic*
类 的全部要点),从而保证只有一个线程可以提高状态标志。
因此,让我们将两者结合起来,使用一个 3 值状态(为简单起见,我使用 AtomicInteger
,但您可以使用 AtomicReference
和真实的 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.
}
}
我需要确保每个实例生命周期只执行一次特定的启动和停止代码,并且实例不能 "restarted"。以下代码是否适用于多个线程可能作用于实例的场景?
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);
}
}
这个解决方案是不够的。考虑这个场景:两个线程同时进入 start()。调用 active.get()
并返回 false
。然后第二个调用 active.get()
并且它也得到 false
。在这种情况下,他们都将继续。然后第一个将 active 设置为 true。此时的第二个也将 active 设置为 true,它们都将继续到应该 运行 一次的其余代码。
解决方案可能是这样的:
public final class MyRunnable {
private final AtomicBoolean active = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
public void start() {
synchronized (this) {
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() {
synchronized (this) {
if (closed.get()) {
throw new IllegalStateException("Already stopped!");
}
if (!active.get()) {
throw new IllegalStateException("Stopping or already stopped!");
}
// My one-time stop code.
closed.set(false);
active.set(false);
}
}
}
出于两个原因,我会选择单一的 3 值状态。
首先,在 active,closed
"tuple" 的 4 个可能值中,只有 3 个有意义,将两者都设置为 true
会导致(可能是良性的,但仍然)无效状态.您可能会认为它纯粹是迂腐,但清晰的设计通常会带来其他好处。
这直接将我们引向了第二个更可怕的原因:
active.set(false);
// <-- what if someone calls start() here?
closed.set(true); //I assume you wanted to set it to true
正如你从我的评论中看到的那样,你在那里有一个弱点,有人可以想象在你将 active
设置为 false
之后但在你之前调用 start()
将 closed
设置为 true
.
现在你可以直接说"okay, let's swap the two then and set closed
first",但是接下来你要解释为什么这两个肯定不会被JVM重新排序。您最终可能会将两个标志都设置为 true
,从而导致上述 "invalid state"。
这里还有另一个单独的问题:您遵循的模式是调用 get()
来检查值,然后 set()
稍后再调用它。由于 start()
1000 次,所有这些都将 active
视为 false
。您需要使用 compareAndSet
代替,其中 是 原子的(这是 Atomic*
类 的全部要点),从而保证只有一个线程可以提高状态标志。
因此,让我们将两者结合起来,使用一个 3 值状态(为简单起见,我使用 AtomicInteger
,但您可以使用 AtomicReference
和真实的 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.
}
}