With the introduction of the new Java Memory Model (JMM) in Java 5 (JSR-133) a lot of unclearities have been removed from Java regarding immutable classes and thread safety. Under the new JMM it is now perfectly clear that the immutable nature of a class doesn’t guarantee that it is thread safe. Take a look at the following class:
public class MyInt{
private int x;
public MyInt(int y){
this.x = y;
}
public int getValue(){
return x;
}
}
Reorderings and Visibility
MyInt is not thread safe by nature, even though it is immutable. I hope you wonder why. To understand what can go wrong, I have to introduce 2 concepts:
- reorderings: the compiler (Javac, JIT, CPU etc) is allowed to reorder instruction to improve performance. To name a few techniques that rely on reordering:
- locality: reads/writes to variables can be grouped to increase the chance of cache hit
- dynamic scheduling: to prevent that a CPU has to wait (and do nothing) for instructions in the pipeline (maybe memory access is delaying), it can execute unrelated instructions.
There are more techniques, and you are never sure which techniques are used because Java is platform independent. You just need to remember that instructions can be reordered unless this is prevented.
- visibility: inside a single thread all reads/writes to a variable need to be visible, but no guarantees are made with normal variables that changes made in by some thread to a variable, are visible in another thread. Only when there is a happens-before edge between a write and a read, you are sure that the change is visible. The reason why this strange behavior exists is performance. Because of the strong memory coherence most CPU have, visibility problems normally won’t occur that often.
So what can go wrong with MyInt?
Constructors are not treated special by the compiler (JIT, CPU etc) so it is allowed to reorder instructions from the constructor and instructions that come after the constructor. In the case of the MyInt it is allowed that the assignment of a newly created instance of of MyInt is assigned to a variable before the constructor has run. This reordering is never visible in the executing thread (within-thread if-serial semantics), but if another thread is going to read x, it could see a partially created MyInt and sees 0 (the default value of an int member variable) at one moment, and the next moment it sees the correct value for x. This problem can be fixed by making x final because this prevents reordering. Under the old JMM, making the field final even is not a solution.
Another problem is that the write to x by the constructing-thread, and the read from x by another thread, doesn’t need to have a happens-before edge. If this edge is missing, no guarantees are made that a change made in one thread, is visible in another thread (in other words, x is not safely published). The consequence is that the other thread doesn’t need to see the constructed value (ever!) and still sees the initial value. This visibility problem can be solved by making x final (volatile would also be a working solution).
Conclusion
Not all immutable classes are thread safe by definition, only proper created ones are. The simplest thing that can be done is making variables final, if these can be set in the constructor. In case of setters, reordering and visibility problems can be prevented by making the field volatile or make sure that the field is always used in a synchronized context (eg synchronized getter/setter or a Lock).
The new JMM has a new and very useful property: save hand off. It allows objects with visibility and reordering issues to be used safely in a multi threaded environment because reorderings and visibility problems are prevented. Not properly created immutable classes can be passed through such a structure without worrying about these issues. But if you have the chance, try to do it good from day one.
If you want to learn more about the JMM, I suggest reading “Java Concurrency in Practice”.
Posted by pveentjer
Posted by pveentjer
Posted by pveentjer