![](/img/trans.png)
[英]What's the risk if I replace AtomicBoolean with volatile in this code?
[英]Controlling an instance's state with AtomicBoolean
我需要确保特定的启动和停止代码在每个实例生命周期内仅执行一次,并且该实例不能“重新启动”。 以下代码是否适合于多个线程可能作用于该实例的情况?
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);
}
}
由于两个原因,我会选择一个单一的三值状态。
首先,在active,closed
“元组”的4个可能值中active,closed
只有3个有意义,将两者都设置为true
导致(可能是良性,但仍然是)无效状态。 您可以将其视为纯装饰,但是清晰的设计通常会带来其他好处。
这使我们巧妙地得出了第二个更可怕的原因:
active.set(false);
// <-- what if someone calls start() here?
closed.set(true); //I assume you wanted to set it to true
正如你可以从我的评论看,你已经有了一个薄弱环节在那里,有人可以想见,调用start()
你设置后active
到false
,但之前设置的closed
至true
。
现在您可能只是说“好吧,让我们先将它们交换掉,然后将其设置为closed
”,但是接下来您必须解释为什么JVM绝对不会对二者进行重新排序。 最后,您可能会将两个标志都设置为true
,从而导致上述“无效状态”。
这里还有另一个单独的问题:您遵循的模式是调用get()
来检查值,然后set()
为其他值。 正如PetrosP指出的那样 ,这不是原子操作,您可以调用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.
}
}
该解决方案是不够的。 考虑这种情况:两个线程同时进入start()。 一个调用active.get()
并返回false
。 然后第二个调用active.get()
,它也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);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.