I need to ensure that a particular start and stop code is executed only once per instance lifecycle, and that the instance cannot be "restarted". Is the following code adequate for a scenario where multiple threads may be acting upon the instance?
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);
}
}
I would go with a single 3-valued status for two reasons.
Firstly, out of the 4 possible values of the
active,closed"tuple" only 3 make sense, setting both totrueresults in a (possibly benign, but nevertheless) invalid state. You may dismiss it as pure pedantry, but a clear design often brings other benefits.This leads us neatly to the second, scarier reason:
As you can see from my comment, you've got a vulnerable spot there, someone could conceivably call
start()after you've setactivetofalsebut before you setclosedtotrue.Now you may just say "okay, let's swap the two then and set
closedfirst", but then you have to explain why the two would definitely not be reordered by the JVM. And you'll end up with potentially both flags set totrue, resulting in the "invalid state" outlined above.There is another, separate problem here: the pattern you follow is to call
get()to check the value and thenset()it to something else later. As PetrosP pointed it out, this isn't an atomic operation, you can callstart()a 1000 times with all of them seeingactiveasfalse. You need to usecompareAndSetinstead, which is atomic (this is the whole point of theAtomic*classes), and thus guarantees that only one thread can ever advance the status flag.So let's combine the two, using a single 3-valued status (I've used
AtomicIntegerfor simplicity, but you can useAtomicReferenceand a trueenum) andcompareAndSet():