Here’s my notes on the google talk on the java memory model, given by Jeremy Manson.
- Don’t try to avoid using synchronized, and other java concurrency abstractions. it’s very difficult to get right, and usually doesn’t buy you very much to try. even Doug Lea gets this stuff wrong.
problem:
example:x=y=0
Thread 1: x=1; j=y
Thread 2: i=x; y=1
how can i=0 and j=0? this is obviously not intuitive, as it appears from the order of the statements that both i and j could never both be 0. however, the compiler/jvm/processor could analyze the assignments as independent events and reverse the order of the statements in the threads (e.g Thread 2: y=1; i=x), leaving us with the possibility that both i and j can end up as 0. the takeaway here is that there are very few assumptions you can make about thread interaction without explicit synchronization.
- don’t rely on locks flushing stuff. releasing a lock only matters if there is a subsequent acquire.
- if a field can be accessed by multiple threads, and at least one of those accesses is a write, you should either use locking to prevent simultaneous accesses, or make the field volatile. synchronization is hard to get right, volatile is a bit harder to get right, try to use any other technique to prevent a “data race”, and you will NOT get it right.
properties of volatile:
- reads and writes go directly to memory
- volatile logs and doubles are atomic
- volatile reads and writes cannot be reordered
- reads and writes become acquire/release pairs.. volatile write happens-before all following reads of the same variable. a volatile write is similar to unlock, a volatile read is similar to a lock.
the danger of not using volatile, is that a non-volatile variable can be optimized by the compiler to be kept in a register instead of written to global memory. this is because compiler optimizations and transformations are performed on single threaded code.
initializing singletons. this code doesn’t work:
Helper helper;
Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper==null)
helper = new Helper();
}
}
return helper;
}
this is common code, but actually broken. the problem with this code is that there is locking on the side of the writer of helper, but not on the side of the reader. it’s possible for someone to come along and read that helper!=null, but get a junk value for helper. the fix is to add a volatile modifier to the declaration of Helper:
private volatile Helper;
even better solution is to use the effective java pattern “Initialization on Demand Holder Idiom” in effective java.
immutable objects are obviously great for thread safety. final fields don’t allow other threads to see an object until construction is complete.
even if you are using a ConcurrentHashMap, if you are doing a get, some stuff, then a put, even though get and put are thread safe, you still need to lock on the “some stuff” section of your code. people who write the JDK even make this mistake, so watch for it.
use @ThreadSafe, @NotThreadSafe, @GuardedBy, @immutable annotations to document your code. some of these are checked by FindBugs.
make code correct, then worry about making it fast. fast concurrent code amounts to reducing sync costs, use java.util.concurrent and java.nio classes, and reducing lock scope.
read Java Concurrency in Practice!!!!