Java threading — best practices
When I first started using threads in Java, I couldn’t find any guide, any “best practices”. I had to sort out my own.
I had seen the data damage caused by failure to synchronize threads, and the delays and mess that result from willy-nilly synchronization. I had decided the answer was to clearly identify where information had to be shared between threads, and to synchronize only where necessary. This remains good advice, but the story has become richer.
Since then, many articles on how to avoid the worst problems have appeared, and the Java packages have changed, to make robust programming easier and to give options for faster execution.
I went through some of the main articles and collected what struck me as the main points.
There are issues with the singleton pattern in multi-threaded environments. Depending on what you want them to do, there are multiple ways to make thread-safe ones.
How you know you’re in trouble
If you’re thinking in terms of tossing in more synchronized
keywords, if you’re thinking in terms of synchronizing enough or
synchronizing more, you don’t know what you’re doing.
Synchronization should be applied precisely where it is needed, and nowhere else. The discipline is to know what those places are.
Results of incorrect synchronization
Failure to synchronize: mangled data, erratic behavior, crashes, etc.
Inappropriate synchronization: very slow operation, pauses, hangs.
Avoiding race conditions, deadlock and other bad behavior
- use local variables in
execute()
rather than instance/class/static variables. -
That is, thread storage should be instantiated in
execute()
so that it can’t be available to other threads.
Otherwise, the variable’s object be somehow guaranteed to be thread-safe.
Most commonly:final
and immutable variables.
Special cases: Atomic classes, andvolatile
variables. - after using
lock()
, alwaysunlock()
. -
lock.lock(); try { // do something ... } finally { lock.unlock(); }
- never call
wait()
outside a loop that checks a wait condition! -
The purpose is to pause execution of the current thread until something has occurred in another thread. Use a “guarded block”, like
synchronized { while( !condition ) { try { wait(); } catch( InterruptedException e ) {} } }
In the other thread where
condition
is altered, a synchronized call tonotifyAll()
will causewait()
in the above to return, so thatcondition
can be checked and used in the first thread.
Right behavior and efficiency
- minimize locking scope
-
This is Rule Number 1.
Much easier to see what must be concurrent in the code.
About efficiency, see Amdahl’s law
- prefer synchronized blocks to synchronized methods
-
Synchronized methods synchronize the current object
this
.
(synchronized methods of static objects synchronize the object class!)
Efficiency
- code to be accessed by different threads should be locked with separate locks
-
(that is, pass different mutex objects in the
synchornized
keyword) - thread pool executors instead of explicitly creating threads
- (if lots of threads are to be created) thread creation is expensive.
- new synchronization utilities rather than wait/notify
-
e.g.
CycicBarrier
,CountDownLatch
andSempahore
seejava.util.concurrent
EspeciallyBlockingQueue
for producer-consumer design. - concurrent collections rather than synchronized collections
-
E.g.
ConcurrentHashMap
rather than a synchronizedHashMap
. ReentrantLock
-
(for high performance code) Lots of tweeking options.
seejava.util.concurrent.locks
Can choose “unfair” lock, which may be faster with multiple threads.
There are other features, such as interruptible locks, try locks, coupleable locks... Callable
vs.Runnable
- (when thread needs to communicate a lot with the invoking thread)
Persistence
- (EJB) max thread count server setting
-
Threads consume server resources,
but if max thread count is lower than the number used by application, performance can be affected.
(? how to debug this?) - (EJB)
@Singleton
- bean maintains state (and identity?) between client invocations
Thread-safe singletons
Usually have a public maker function, to insure the private constructor is called just once, based on the value of a flag. But what if that function is called from multiple threads?
familiar thread-safe singleton.
Instantiates at class load time.
public class S { private static final S theInstance = new S(); private S() { } public static S getInstance() { return theInstance; } }
enum singleton.
Advantage: no need to hide the constructor or provide a
getInstance
.
It may be just superior to the above familiar version.
The constructor can take only class-load-time arguments.
public enum S { theInstance; S() { } }
“double-checked locking” singleton
Thread-safe, fast, lazy-instantiated.
Run-time constructor could use run-time info.
public class S { private static S theInstance; private S() { } public static S getInstance() { if( theInstance == null ) { synchronized( S.class ) // first invocation only! { if( theInstance == null ) theInstance = new S(); } } return theInstance; } }
Interfaces
All the above advice is at the code level, and naturally dependent upon discipline, and therefore fragile. It is very easy to insert another bit of code that does some unsafe communication between threads. There are better methodologies that do away with much of that magic.
Programming to an interface is useful here: by exposing only very limited interfaces between communicating threads, the task of checking that code is thread-safe is greatly simplified. It requires only that the programmer carefully define the communications relationship between the two threads.
The idea is then to identify very limited regions of code in the current thread that might be accessed by other threads, and make an interface that exposes only that. Another thread only accesses the current thread only via this interface. In Java, this results in very tight access control.
For example, it is very common for two threads to communicate as though one is a server and the other is a client. Here a client-server model is in order: one thread sees the other as a client, the other sees the first as a server. The methods requiring synchronization are very clear.
The flip-side of this is: it is very poor practice to expose non-thread-safe methods to another thread at all.
See:
Top 10 Java Multithreading and Concurrency Best Practices