What makes livelock worse than deadlock is that even though no progress is being made,
something is happening. This makes livelock harder to find: there are no threads that show
up as stuck. It means that resources are being used. And just like deadlock, livelock is even
harder to find in a distributed application. The only good thing about livelock is that it is much
less common than deadlock. But it can happen, so you should be aware of the possibility.
This is only the briefest of introductions to the problems and techniques of writing thread-safe
code. There are a number of interesting longer works on the subject, [ 41 ] and I highly recom-
mend reading some of those before plunging into problems that require thread safety. The oth-
er thing to understand about programming with threads is that brute force (even brute force
with a debugger) is not the way to be successful. You have to think, and think hard, about
what could be going on in the program under all the different ways that it could execute. This
is also an area where having your code reviewed by someone else can be a huge aid. There is
nothing like a second set of eyes to help you find race conditions or potential deadlock.
When we looked at the concurrency problems with the StatRecorderImpl , one set of as-
sumptions that could have gotten us into trouble concerned the atomicity of the operations in
some of the methods. Thinking that something as simple as incrementing an integer is thread-
safe has been the cause of many concurrency bugs. Synchronizing such uses can keep our
program correct, but the locking involved can be expensive.
To help in this, the Java libraries now contain a set of atomic data types. These objects can be
used in place of the corresponding data types and provide thread safety in a lock-free fashion.
These objects also provide a number of methods that themselves are guaranteed to be atomic
and because of that allow thread-safe manipulation of these data types.
Let's go back for a moment to our StatRecorderImpl and think about one of the reasons we
needed to synchronize the atBat() method of the BatterImpl object. All that method does
is increment a set of counters. But we couldn't be sure that those increments would be atomic,
and not made incorrect when accessed by multiple threads. The atomic data objects in the
java.concurrent.atomic package do ensure just such atomicity.
Using those data types for our BatterImpl would be possible, but a bit clumsy. We would
need to change the declarations of the counters to look like:
private AtomicInteger atBats;
private AtomicInteger hits;