This notebook contains various useful tips related to threads and concurrency in Java
Use volatile keyword. It is required because of java memory model: some variables can be in registers, that are different between threads. volatile guarantees visibility.
It is interesting that operations on double and long variables are not necessarily atomic (i.e. can be performed as two 32 bit read or write operations). But operations on volatile double or long variables are atomic (starting from java language specification 2nd edition to be precise).
It is not possible to declare array of volatile elements. When array is declared as volatile only reference to this array is considered volatile.
wait() and notify() can be called only inside synchronized block or method. This is necessary to prevent race condition. Usually wait is called like this:
if (condition) {
wait();
} Without synchronization other thread can start
execution just after checking condition, but before wait and condition
can no longer be true when wait() is called.
wait() releases lock before starting to wait and reacquire it
after wait is complete (either because of timeout or notification).
Because of that it is possible for multiple threads to wait for the
single object (remember that wait should be called from synchronized block)
notify() notifies only one waiting thread, it is not possible to predict which one
You should check condition you are waiting for just after call to wait(). This is necessary because condition can be changed by other thread.
notifyAll() notifies all waiting threads. But synchronization lock should be acquired by every waiting thread in any case to return from wait().
In Java5 you can use Condition instead of wait()/notify().
synchronized guarantees atomicity and visibility. Think about multiple processors, caches, reordering of instructions by
compilers and processors and you understand what visibility means.
Java5 class ReentrantLock can serve as replacement to synchronized
Following code (double check locking) is not thread safe:
void doFoo() {
if (this.foo == null) {
synchronized(this) {
if (foo == null) {
foo = new Foo()
}
}
}
foo.invoke()
}
This is because foo.invoke() can be called before constructor Foo() has been completed. Reference to foo can be assigned before it. This solution ignores visibility.
Note: this is no longer true from Java 1.5, but only if foo is volatile. Memory model has been changed. But it is still bad idea to do it DCL.
Swing related issues: - access swing components only in event-dispatching thread (callbacks are executed in this thread)
- swing objects that have not been displayed can be created and manipulated by any thread. After show() is called only even-dispatching thread can access them
- the invokeLater() method can be called from any thread
- the invokeAndWait() method can be called from any thread other than the event-dispatching thread (SwingUtilities.isEventDispatchThread() can be used together with this method)
- the repaint() method can be called from any thread
Old-style collections (Vector, Hashtable) are synchronized.
New style collections (Set, Map, List) are not synchronized. Use Collection.synchronizedList/Set/Map to get synchronized wrapper for collection.
Behavior of Enumeration when underlying collection is accessed from multiple threads is undefined. Iterator will throw ConcurentModificationException in case if iterated collection is modified by other thread. Iterator for CopyOnWriteArrayList/Set will never throw this exception.
Learn about Thread.interrupt(): - blocking method, such as sleep will be interrupted (with throwing appropriate exception) if it is waiting at the moment of calling interrupt()
- you can use isInterrupted() or Thread.interrupted() (with Runable, implemented using currentThread()) to check if interrupt() has been called
- if interrupt() method is called when no blocking method is waiting the only effect - setting interrupted flag in thread, so isInterrupted() would return true. You should check this flag manually.
Behavior of Thread.interrupt() is not consistent between operating systems when it is called during blocking I/O method (like InputStream.read()). Under Unix-like systems method will throw InterruptedIOException, but under Windows it seems that interrupt() has no effect. If you need to interrupt thread during blocking I/O just close appropriate stream.
ThreadGroup class has limited usage. It can be used for security
(i.e. to determine if thread can interrupt or modify priority of other
thread) or to call certain methods (like interrupt()) on all threads from the group. Under JDK <1.5 it also can be used to install exception handler for threads: you should develop your own class that extends ThreadGroup and override uncaughtException(). In Java5 this is no longer necessary because of new Thread.UncaughtExceptionHandler.
You can throw ThreadDeath exception instead of returning from run() method if it is more convinient. Make sure that you clean all resources before throwing this exception.
JVM will exit when only daemon threads are running. Threads created by daemon threads are daemon threads, threads created by user thread are user threads.
Thread.getContextClassLoader() can be useful in certain
situations. F.e. if in J2EE server some class was loaded by server
specific class loader it has no access to application specific classes,
but it can use class loader from thread to access these classes. Usually context class loader is set to context class loader of parent thread, but it is possible to override it using Thread.setContextClassLoader()
Immutable objects (those that have only final fields) are thread safe, i.e you can pass them between threads without synchronization.
Use fair = true for ReentrantLock and ReentrantReadWriteLock to prevent lock starvation (needed only in rare cases).
|