

Problems with the Java 1.4 synchronization model

Java's bread-and-butter means of synchronization– the synchronized method– has some advantages. It's reasonably easy to understand and implement, and generally allows you to write thread-safe code without getting too bogged down in the details of what the JVM does "under the hood" to synchronize data. Of course, this is also one of its potential downfalls– a failure to understand exactly what synchronized does has led to programmers using incorrect idioms such as "double-checked locking" or the notion that "you only need to synchronize on the write, not the read". But even in a perfect world where every programmer fully understands the synchronized keyword, as a method of synchronization it has some shortcomings:

  • For some purposes, it's quite a heavy-handed means of synchronization. For every object synchronized on, the JVM has to keep track of 'housekeeping' information, such as which thread owns the lock and how many times it has acquired it. And recall that every time any lock is acquired or released at the beginning and end of a synchronized block, all cached variables must be synchronized with main memory. There's no way to tell the JVM "you only need to synchronize this one", nor is the JVM allowed to make such a decision.
  • Relating to the previous point, there is no way to gives performance hints to the JVM about how we expect the code in our synchronized block to behave. We'll explain this point in more detail below.
  • synchronized is an all-or-nothing thing. Once your thread attemps to enter a synchronized block, it will hang forever until the lock is available. In the real world, we often want to do things like "wait for up to 2 seconds for the lock on the cache, else don't bother caching", for which we need a more complex workaround. There's also the more serious risk of deadlock: two threads each holding on to the lock that the other thread needs to continue. In a complex server application that synchronizes on many different objects in many points in the code, guaranteeing that deadlock does not occur can be a very tricky issue.
  • Once a thread does enter a synchronized block, we have no good way of asking the JVM if our thread had to wait for the lock or not. (We could time it, but calling System.currentTimeMillis() twice as we'd have to can itself burn up to a millisecond of CPU time and in any case probably isn't accurate enough.) So from our normal running program, we can't profile lock contention and spot bottlenecks.

A limitation of pre-Java 5 as a platform is that the standard class library doesn't provide implementations of some common synchronization idioms. For example, a common use of synchronization in server applications is to manage a shared resource pool (for example, of database connections). Rather than a simple lock, what we really need in this case is a "permission" system that says "allow up to N threads to hold a lock at any one time" (because there are N resources available in the pool). Idioms such as this can of course be constructed pre Java 51 (else nobody would have been using Java to run servers!) but not necessarily very efficiently. And without a standard library implementation, different programmers have been forced to re-invent the wheel, possibly in buggy ways.

So how does Java 5 improve this situation? On the next pages, we look at Synchronization under the hood, and how Java 5 improves on it.

1. For some suggested pre-Java 5 implementations of idioms such as barriers, sempahores etc, see Wellings, A. (2004), Concurrent and Real-Time Programming in Java.

Article written by Neil Coffey (@BitterCoffey).

comments powered by Disqus


 LetterMeister (word puzzle game for iPhone)
 Currency Quoter (currency converter/predictor)
 French Vocab Games for iPhone/iPad
 Vocabularium: create Spanish vocab podcasts

Copyright © Neil Coffey 2013. All rights reserved.