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.
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.
Failure to synchronize: mangled data, erratic behavior, crashes, etc.
Inappropriate synchronization: very slow operation, pauses, hangs.
execute()
rather than instance/class/static variables.execute()
so that
it can’t be available to other threads.final
and immutable variables.volatile
variables.
lock()
, always unlock()
.lock.lock(); try { // do something ... } finally { lock.unlock(); }
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 to notifyAll()
will cause
wait()
in the above to return, so that
condition
can be checked and used in the first thread.
this
.synchornized
keyword)
CycicBarrier
, CountDownLatch
and Sempahore
java.util.concurrent
BlockingQueue
for producer-consumer design.
ConcurrentHashMap
rather than a synchronized
HashMap
.
ReentrantLock
java.util.concurrent.locks
Callable
vs. Runnable
@Singleton
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?
Instantiates at class load time.
public class S { private static final S theInstance = new S(); private S() { } public static S getInstance() { return theInstance; } }
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() { } }
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; } }
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