vmlens shows you all detected race conditions in the Race Conditions, Deadlocks View. To analyze a race condition expand the race condition, to see the stack traces of the reading and writing threads:
The example above shows you a race at field bounds of class sun.reflect.generics.reflectiveObjects.TypeVariableImpl. Thread-0 is reading and Thread-3 is writing to this field. Let us look at the source code by using open declaration on the method getBounds:
This shows us the following source code:
package sun.reflect.generics.reflectiveObjects; // import statements omitted public class TypeVariableImpl extends LazyReflectiveObjectGenerator implements TypeVariable { // upper bounds - evaluated lazily private Type[] bounds; public Type[] getBounds() { // lazily initialize bounds if necessary if (bounds == null) { FieldTypeSignature[] fts = getBoundASTs(); // get AST // allocate result array; note that // keeping ts and bounds separate helps with threads Type[] ts = new Type[fts.length]; // iterate over bound trees, reifying each in turn for ( int j = 0; j < fts.length; j++) { Reifier r = getReifier(); fts[j].accept(r); ts[j] = r.getResult(); } // cache result bounds = ts; // could throw away bound ASTs here; thread safety? } return bounds.clone(); // return cached bounds } // other fields and methods omitted }
The class TypeVariableImpl modifies the not volatile field bounds without synchronization In line 15 and 30 the field bounds
is read and in line 27 it is written. If the code is executed in the given order everything is o.k. But if some component re-orders the statements, the array is not completely initialized. In pseudo code the method getBounds looks like this:
if instance variable bounds is null { set local variable ts to new Array initialize the array set instance variable bounds to the local variable ts } return instance variable bounds.clone
If the statements get reordered another thread sees an uninitialised array:
Thread A | set local variable ts to new Array | |
Thread A | set instance variable bounds to the local variable ts | |
Thread B | if instance variable bounds is null | |
Thread B | Thread B return instance variable bounds.clone // the array is not yet completely initialized |
One such component is the cache system of the CPU. ARM compatible processors like in smart phones or the Raspberry Pi reorder reads and writes to improve performance, leading to a scenario as described above.
To fix the race condition you can either declare the method getBounds as synchronized or declare the field bounds as volatile. Using synchronized makes sure that the field bounds only gets assigned once. Using volatile on the other hand is faster.