pagetop
Javablog
by Java coders, for Java coders RSS

Variable visibility across threads and final fields

June 15th, 2007 by Sam

I just finished reading Java Concurrency in Practice (JCIP) (buy on Amazon) and realised that every piece of threaded code I’ve ever written is probably broken. The book is pretty tough reading and covers the concurrent libraries in quite a bit of detail (the implementation of nonblocking concurrency in ConcurrentLinkedQueue is possibly the most mind-blowing code I’ve ever seen). The hardest hitting lesson that I learnt was about visibility of fields across threads, and in particular across multiprocessor/multicore systems.

In the following code snippet, we have a Runnable that keeps doing stuff until another thread calls stopIt(). If you think it is thread-safe, then you need to read JCIP.

boolean stop = false;
 
public void run() {
    while(!stop){
        doStuff();
    }
}
 
public void stopIt(){
    stop = true;
}

The reason why it is broken is because of the visibility of the field stop. In the Java Memory Model (JMM), different threads (and in particular, different CPUs) are allowed to take local caches of fields. It may be that the thread running run may never know to re-cache the value of stop and it will run forever. JCIP gives a detailed rundown of when threads take new snapshots of the fields they are accessing, but the simple answer is stop must be made volatile, or it must be read from within a synchronized block.

A simple way to avoid the pitfall is to use the final keyword when declaring fields. Obviously this doesn’t work for primitive types, but there are atomic versions of the primitives in java.util.concurrent.atomic that also provide some additional atomic operations (note that i++ on an int is actually 3 operations, not one, so best use incrementAndGet. If something has been declared final, then the JMM knows that it always has the most up to date reference to the object and that it will never change. This has led to a frenzy of adding final to everything we possibly can in our codebase :-).

This is only the tip of the iceberg in terms of what the book covers… it explores alternatives to using synchronized with emphasis on scalability. One of my personal favourites is the use of ReadWriteLock for making maps/databases block only on writing, with multiple concurrent reads.

private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
 
public V get(K key){
    readLock.lock();
    try {
        // do lookup and return
    } finally {
        // don't forget to unlock
        readLock.unlock();
    }
}
 
public void set(K key, V value){
    writeLock.lock();
    try {
        // do setting and return
    } finally {
        // don't forget to unlock
        writeLock.unlock();
    }
}

This entry was posted by by Sam on Friday, June 15th, 2007 at 11:15 am, and is filed under Advanced, Concurrency, Java, Locks, Threads, final, volatile. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.



2 comments on “Variable visibility across threads and final fields”

First of all: adding finals to fields where you can, is a good thing (I love constructors and hate constructor-setters).

But visibility (and reordering) also can be prevented by a property that is supported in Java 5 and higher: save handoff (aka piggybacking on synchronization).

See JCIP 16.1.4. Piggybacking on Synchronization

This makes it possible to use objects with visibility and reordering problems in a concurrent environment. A lot of structures support this feature, eg: BlockingQueue’s.

So if you have a ‘person’ with visibility/reordering problems, by transfering it over a blocking queue, the object can be used safely by multiple threads (as long as only one thread is working on it at any given moment)

Perhaps a better motivation for thread-safety is compiler optimisation. The original Java Memory Model was based upon a model of CPU caches. It didn’t really work. Optimisers will do weird stuff, and you can’t really try to work out what they will do. The temptation with the hardware mental model is to make excuses as to why the code should always work despite not complying with the memory model.

Leave a comment

Markdown is supported.

To include code snippets in your comment, use

<pre><code># lang java
... code here ...
</code></pre>

or use 4 spaces at the start of the line instead of using code and pre tags.

Comment feed: RSS