Warning: Introductory rant follows. You may want to skip down three paragraphs.
One of things that got developers excited back in the early days of
Java1 was its
built in support for threading. This came via java.lang.Thread, the
keywords synchronize and synchronized,
the (well thought-out but non-intuitive) methods wait()
and notify(),
and a handful of other features. Sun then made sure that everyone knew how to use these mechanisms
properly - through articles, conferences,
certification and the venerable Java
Tutorial.
However, after a few long debugging sessions recently, I have come to the conclusion that there are quite a number of Java programmers out there who do not have a solid grounding in Java threads2.
In the hope of easing any future pain, and in the hope of making the world a better place, here are the heuristics that I use when building or debugging threaded code.
Heuristic 1: If there is any possibility one thread may read a value that
another is changing, you need at least one synchronize or
synchronized somewhere.
Rationale: When two threads operate on the same values simultaneously,
they often end up doing things that the programmer did not expect. The Java
Language Specification discusses
some of the surprising kinds of behaviours that can emerge when two threads interact
without coordination. There are no atomic operations in Java (e.g. a Test And Set operation) that are useful for
coordinating access to values between threads, therefore you need to use synchronize to avoid these surprising behaviours.
Corollary 1a: Even if threads "mostly just read" a value, you still
need to use synchronize.
Rationale: "Mostly just read" is another way of say "sometimes
writes". Not using synchronize is just asking for your program to fail in the most interesting and unrepeatable ways.
Corollary 1b: Even if you are 100% sure that two threads accessing
the same values won't
interact unexpectedly because you hand-checked all possible execution paths, use synchronize anyway.
Rationale: There are so many, many ways that two threads can mis-behave in this situation it's better to be safe than sorry. There's also the next programmer to consider - and he may not be as smart as you.
Heuristic 2: The fewer synchronized blocks you have, the less chance there is of introducing a hard-to-find threading bug into your code.
Rationale: The more synchronized blocks there are, the more different permutations of execution orders there are for multiple threads. The more permuations you
have to check, the more likely it is you (or the next programmer) will leave or
inject a bug.
There are several ways to reduce the number of
synchronized blocks in a piece of multi-threaded code. Here are three: (a) don't use
threads3; (b) pack all the code that access the shared values into a single
object, and synchronise on that; and (c) instead of scattering
synchronize blocks throughout a method, declare the method
synchronized.
Heuristic 3: Using Thread.stop(),
Thread.suspend() or Thread.interrupt() is
always the wrong thing to do.
Rationale: These methods halt a Thread's execution at arbitrary locations in the program, quite probably leaving the objects it was accessing in an inconsistent state. Elliotte Rusty Harold wrote about this.
Heuristic 4: Using Object.wait() or
Object.notify() is almost always the wrong thing to do.
Rationale: For typical algorithms, the waiting effect
of synchronize is exactly what is required. wait() and notify() do have
legitimate uses, particularly in producer/consumer scenarios. However, every
use that I have seen (outside of tutorials and UI code) has been mis-guided. 4
Heuristic 5: Don't optimise prematurely. First make it work, then make it fast.
Rationale: A golden-oldie of programming heuristics, this one is doubly pertinent to threading. The threading mechanims introduce myriad more ways for your program to go wrong, so avoid them if you can. If you need to use threads, use simple constructs such as coarse grained synchronisation. If that's not fast enough, then go back to the points where it is slow and fix those.
That's all. Use in conjunction with liberal doses of diligent design, careful coding and thorough testing.
1I am old enough to remember the early days of Java.
2This isn't supposed to be a dig at "the quality of Java programmers these days" or some such. There's plenty of good reasons why developers don't pay as much attention to threading as they used to.
3Far too much code uses multiple threads when it would be better off with just the one.
4Another heuristic: If a thread is waiting for another thread to finish using a resouce, plain old synchronize is what is needed. If a thread is waiting for another thread to produce something, then wait/notify may be needed.
Comments