Many of the gurus out there are already aware of this, but the auto-increment
and auto-decrement operators are not thread safe in Java. I was working in some
code recently where the only mutation that was occurring was an auto-increment
call to an index. After wrapping all of the interactions with this
variable in synchronized blocks, I looked at the damage and thought, “I wonder if I
can just make this
volatile and be done with it.”
Using volatile to solve this particular problem would require that the operation be atomic. I wasn’t sure of this, so I thought I’d test it out myself. The first thing I did was create a very simple class so I could take a look at the byte code that was generated.
This very simple class just provides methods that wrap the pre and post increment operators. If we take a look at the byte code generated for this class file with javap we can see the issue come leaping out like a four-headed chromatic dragon:
The problem above is that these operators are implemented in byte code as read-modify-write. This creates a problem when multiple threads are operating on the same piece of state. Consider the following:
- Thread 1: getfield #2; // Thread 1 gets the value ‘0’
- Thread 2: getfield #2; // Thread 2 also gets the value ‘0’
- Thread 1: dup_x1, iconst_1, iadd, putfield #2 // Add ‘1’ and write it back
- Thread 2: dup_x1, iconst_1, iadd, putfield #2 // Add ‘1’ and write it back
At this point, the shared variable will have a value of ‘1’, despite having two auto-increment operations called on it! This can manifest itself in any number of nefarious ways. To better illustrate that this will happen if not guarded against, I’ve written a test and posted it on github. You can grab the test code here
Once you have the code, build and run it (assuming you have gradle) with:
You’ll find that it breaks pretty quickly. Feel free to vary the number of threads and the number of iterations. Even for values as low as 2 threads and 20 iterations, this will break pretty quickly.
This is one of those places where the syntax obscures a complex operation - something that occurs frequently in java (think auto boxing). If you’re not sure how something behaves, it’s worth busting out javap on the generated class file to find out exactly what’s happening.