The problem is that by default, the JVM is allowed to perform a lot of tricks when working with multiple threads. It may e.g. reorder statements, duplicate variables across threads and other things.
What can go wrong in your MutableInteger:
- Thread 1 calls set(5)
- Thread 2 calls set(3)
- Thread 1 calls get() and receives 5 (not 3) as the result, even though Thread 2 has finished calling set(3) on it.
How is SynchronizedInteger better? Entering a synchronized method (or block) forces the JVM to do two things:
- only one thread may enter at a time, all others have to wait (exclusive lock) AND
- all changes to the synchronization target are made visible to all threads immediately when the block is exited
So in the example above, Thread 1 will receive 3 when calling get(), not 5 as before.
By the way, for primitives (int, char, float...) you can use the volatile keyword to force changes to be visible to all threads immediately. Alternatively, use the builtin AtomicInteger and friends for better performance than synchronized methods.