سؤال

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.

هل كانت مفيدة؟

المحلول

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.

نصائح أخرى

The thing about race conditions is that they don't necessarily happen if you don't do proper synchronization -- indeed, quite frequently it'll work just fine -- but then one year later, in the middle of the night, your code will crash with a completely unpredictable bug that you can't reproduce, because the bug only appears randomly.

Race conditions are so insidious precisely because they don't always make your program crash, and they trigger more or less randomly.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top