Abstract: CountDownLatch is easy to understand, but can be hard to use, especially the await() method that throws InterruptedException. In this newsletter we show how we can create our own synchronizer based on the AbstractQueuedSynchronizer.
Welcome to the 292nd edition of The Java(tm) Specialists' Newsletter, sent to you from, yes you guessed it, the stunning Island of Crete. Greece took the painful path of a strict lockdown from November until May, together with making it easy to get vaccinated for those who wanted to. The hope was that tourists would come flocking to Greece during the summer months, bringing much needed economic relief. It seems to have paid off. 2021 was not nearly as good as 2019, but there are a lot of tourists here, enjoying the beaches, fine food, good weather. We even have a miniature version of JCrete this year, consisting of 100% Java Champions, all vaccinated of course.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
A few months ago, I started recording detailed walkthroughs of how the concurrency utilities work in Java. So far I have created a course about the ArrayBlockingQueue and the CopyOnWriteArrayList/Set.
The next course that I want to create is about the
AbstractQueuedSynchronizer
. I had known about the class for
a very long time and I knew that it was at the cornerstone
of many concurrency classes. However, I had never investigated
how it truly worked, until now. Recently I was looking at
a new open source project that provides a parser for log
files and they were using the CountDownLatch
in a rather
awkward fashion:
private final CountDownLatch deployed = new CountDownLatch(1); public void awaitDeployment() { try { deployed.await(); } catch (InterruptedException e) { LOGGER.throwing("JVMEventSource", "awaitDeployment", e); } } public void start() { deployed.countDown(); }
I shudder when I see InterruptedException
handled like this.
Interruptions are orthogonal to the code and we never know
when a thread might be interrupted, nor which other thread
has issued the interrupt. We also do not know who might want
to know later whether our thread was interrupted. Before
throwing the InterruptedException
, the interrupt status on
the current thread is cleared. Thus we would lose that status
on the thread if we catch the InterruptedException. On the
other hand, we also do not want to propagate the exception
from the method, otherwise the caller will have to deal with
it. Most likely we actually want to block the caller until
we are completely deployed and ready for action. When I saw
that code, I immediately refactored it into an
uninterruptible wait, like so:
private final CountDownLatch deployed = new CountDownLatch(1); public void awaitDeployment() { boolean interrupted = Thread.interrupted(); while(true) { try { deployed.await(); if (interrupted) Thread.currentThread().interrupt(); break; } catch (InterruptedException e) { interrupted = true; } } } public void start() { deployed.countDown(); }
After inspecting the rest of the project, I found 3 other
places that were coded in a similar fashion. In all cases
the CountDownLatch
had size one and these were used as a way
to indicate whether the service had been started up. A
starting gun, if you will. Even though the code above will
work, it does have the disadvantage that every time the
thread is interrupted, a new InterruptedException
would be
constructed. Exceptions are particularly expensive to create
because of the stack trace. It is typically best to avoid
that cost.
Fortunately I had already done some research for my new
mini-course on the AbstractQueuedSynchronizer
and thus knew
that it would be trivial to make a new synchronizer, similar
to the CountDownLatch
, but which would do precisely what we
were looking for. Since the CountDownLatch
is in the public
domain, we are allowed to copy and paste the code to our
heart's content. I did that to get the initial structure
and then did some refactoring to simplify it a bit. One thing
to note is that some of the parameters are not used in this
synchronizer. I thus name them "unused" so as to avoid
confusion.
import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** * A StartingGun for services, where we can wait for one * particular service to start up. * * Instead of {@link java.util.concurrent.CountDownLatch}, we * have a custom concurrency utility called StartingGun, which is * like a CountDownLatch with a count of 1 and where the * awaitUninterruptibly() method does not throw an exception. * Similarly to the CountDownLatch, this uses the {@link * AbstractQueuedSynchronizer} for the synchronization. * * @author Dr Heinz M. Kabutz */ public class StartingGun { private static final int UNUSED = 0; /** * Synchronization control For StartingGun. * Uses AQS state to represent whether we are ready. */ private final AbstractQueuedSynchronizer sync = new AbstractQueuedSynchronizer() { private static final int SUCCESS = 1; private static final int FAILURE = -1; private static final int READY = 1; protected int tryAcquireShared(int unused) { return (getState() == READY) ? SUCCESS : FAILURE; } protected boolean tryReleaseShared(int unused) { setState(READY); return true; } }; /** * Wait for the starting gun to fire, without propagating the * InterruptedException. */ public void awaitUninterruptibly() { sync.acquireShared(UNUSED); } /** * Indicate that the service is ready for operation. */ public void ready() { sync.releaseShared(UNUSED); } }
My StartingGun
has a few minor differences with the
CountDownLatch
. First off, we call sync.acquireShared()
,
whereas the CountDownLatch
calls
sync.acquireSharedInterruptibly()
. This means that interrupts
are saved until we are done with waiting for the state,
without causing unnecessary exceptions.
Also, since this is meant to be used with a single service,
we don't need a count. It is more like a single boolean latch
that is either open or closed.
Here is an example of how I used it to simplify the code:
private final StartingGun deployed = new StartingGun(); public void awaitDeployment() { deployed.awaitUninterruptibly(); } public void start() { deployed.ready(); }
Nice and easy!
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.