Your code is broken in several ways. You seem to expect AtomicReference
to fix your race conditions magically but all it provides is, as the name suggest, an atomic access to a reference. If you access that object multiple times you have multiple accesses each of them being atomic but together still not being thread safe.
First example:
updater.set(new Updater());
try {
SwingUtilities.invokeAndWait(updater.get());
} catch (InvocationTargetException | InterruptedException ex) {
}
Both, the set
and the following get
calls are atomic, but who says that the reference you will receive on get
is still the one you have set
previously? You might, for example, receive an even newer Updater
instance which is scheduled by another thread leading to the situation that one Updater
is never sent to the EDT but the other twice.
Second example:
if (null != currProcedure.get()) {
currProcedure.get().cancel(true);
}
if (null != updater.get()) {
updater.get().cancel();
}
Same error two times. You are checking the result of AtomicReference.get()
against null
but being non-null
on that invocation, who says that it is still non-null
on the next invocation? You have that code in a synchronized
block but since the same variables are accessed by other threads, e.g. from within doProcedure()
without synchronization, it does not provide any protection. Maybe you never reset the reference to null
(which would be another error) so it’s no problem here but it clearly shows a misunderstanding of how to use AtomicReference
s.
Further, you say you are using it like this:
Procedure p = new Procedure();
p.execute();
p.execute();
So you are invoking execute
right one after another and these execute methods try to cancel whatever they find in updater
immediately but at this time the background is likely executing your “bunch of stuff” and has not managed to set its updater
(as Harald has pointed out). So both execute
calls might see null
or a very outdated Updater
instance and afterwards the background thread sets the first Updater
and later on the second Updater
, none of them being ever canceled. Note that the interruption of the background thread ends the waiting part of invokeAndWait
but does not cancel the execution of the provided runnable.
Since you are using AtomicReference
you should start really using it. The atomic updates are the key feature to implement the intended logic, e.g.:
Updater newUpdater=new Updater();
Updater old=updater.getAndSet(newUpdater);
if(old!=null) old.cancel();
SwingUtilities.invokeLater(newUpdater);
By reading the old updater and setting the new atomically you ensure that each new Updater
is paired with the potential cancellation of an old one. And by keeping the values in local variables instead of reading them multiple times you ensure that there can be no in-between update changing the references. This works even with multiple background threads and does not need additional synchronized
blocks.
The call to schedule the Updater
has been changed to invokeLater
, because in the case of a single threaded executor waiting for the completion of the Updater
would imply that the thread never comes to set the new Updater
and canceling the old one.
Note that the Updater
itself should set the updater
reference to null
on completion. There is no point in canceling an Updater
which has already completed but since the updater
reference is a shared reference which might exist far longer than the (intended) life time of the Updater
it should be cleared so it can be garbage collected immediately instead of lying around until the next Updater
is set.