Search code examples
javaconcurrencysynchronizationsynchronizedconcurrent-programming

Java Synchronized Method and Block


I'm trying to understand synchronization of multiple threads in Java more fully. I understand the high level idea behind the use of the synchronized keyword, and how it provides mutual exclusion among threads.

The only thing is that most of the examples I read online and in my textbook still work correctly even if you remove the synchronized keyword which is making this topic more confusing than I think it needs to be.

Can anyone provide me with a concrete example of when not including the synchronized keyword will produce erroneous results? Any information would be much appreciated.


Solution

  • You can usually trigger a race condition by increasing the number of iterations. Here's a simple example that works with 100 and 1,000 iterations but fails (at least on my quad-core box) at 10,000 iterations (sometimes).

    public class Race
    {
        static final int ITERATIONS = 10000;
        static int counter;
    
        public static void main(String[] args) throws InterruptedException {
            System.out.println("start");
            Thread first = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < ITERATIONS; i++) {
                        counter++;
                    }
                }
            });
            Thread second = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < ITERATIONS; i++) {
                        counter++;
                    }
                }
            });
            first.start();
            second.start();
            first.join();
            second.join();
            System.out.println("Counter " + counter + " should be " + (2 * ITERATIONS));
        }
    }
    
    >>> Counter 12325 should be 20000
    

    This example fails because access to counter is not properly synchronized. It can fail in two ways, possibly both in the same run:

    • One thread fails to see that the other has incremented the counter because it doesn't see the new value.
    • One thread increments the counter between the other thread reading the current value and writing the new value. This is because the increment and decrement operators are not atomic.

    The fix for this simple program would be to use an AtomicInteger. Using volatile isn't enough due to the problem with increment, but AtomicInteger provides atomic operations for increment, get-and-set, etc.