我需要确保每个实例生命周期只执行一次特定的开始和停止代码,并且不能“重新启动”实例。对于多个线程可能对实例进行操作的场景,以下代码是否足够?
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);
}
}发布于 2016-03-18 13:44:31
因为两个原因,我会选择一个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正如您从我的评论中可以看到的那样,在您将start()设置为false之后,但在将closed设置为true之前,可能会有人调用closed。
现在您可能会说“好吧,让我们先交换两个,然后设置closed”,但是接下来您必须解释为什么JVM肯定不会重新排序这两个。最后,您可能会将两个标志设置为true,从而导致上面概述的“无效状态”。
这里还有另一个单独的问题:您遵循的模式是调用get()来检查值,然后稍后将其转换为其他值。作为PetrosP pointed it out,这不是一个原子操作,您可以调用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.
}
}发布于 2016-03-18 13:43:24
这个解决方案是不够的。考虑一下它的场景:两个线程同时进入start()。一个调用active.get(),然后返回false。然后第二个调用active.get(),它也得到false。在这种情况下,它们都会继续下去。然后,第一个将活动设置为true。此时的第二个代码也将活动设置为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);
}
}
}https://stackoverflow.com/questions/36085736
复制相似问题