2

If int incrementing/decrementing operations are not atomic in Java 6, that is, they are said to be performed in several steps (read the value, increment, write etc), I would like to see a piece of code that will demonstrate how multiple threads can affect a single int variable in a way that will corrupt it entirely.

For example, elementary steps include, but not cover all, these: i++ ~= put i to the register; increment i (inc asm operation); write i back to memory;

if two or more threads interleave during the process, that would probably mean that the value after two consequent calls to i++ will be incremented only once.

Can you demonstrate a piece of code in java that models this situation in multithreading environment?

EugeneP
  • 21
  • 2
  • If you need this for some actual program, just synchronize access to any data shared between threads. If you need it for some study, then I can't help. – Zds Aug 12 '11 at 08:13
  • according to http://stackoverflow.com/questions/1006655 are `int` operations atomic (while e.g. `long` operations *may* be atomic) – Andre Holzner Aug 12 '11 at 08:13
  • 1
    `int` read/write operations are atomic, however `++` is not atomic. – Peter Lawrey Aug 12 '11 at 08:17
  • Yes, but also note even if they are atomic, they might not be visible across threads - at least `volatile` is needed. – Tomasz Nurkiewicz Aug 12 '11 at 08:39
  • @Tomasz Nurkiewicz Could you explain "might not be visible across threads". May it result in data ovewriting, like in above example or it's just a reading problem ? – EugeneP Aug 12 '11 at 09:23
  • See [this](http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html) article, also there are several question on SO about `volatile`. Ont thread writes to the variable but other threads still see the old value - so it might corrupt your data in some circumstances. – Tomasz Nurkiewicz Aug 12 '11 at 09:58
  • @ Tomasz Nurkiewicz Overall I conclude that atomic operations are guaranteed to be seen among threads only with volatile keyword. Still, volatile does not resolve an issue with non-atomic operations, as i++. Correct ? – EugeneP Aug 12 '11 at 10:14

3 Answers3

3
public class Test {
    private static int counter;

    public static void main(String[] args) throws InterruptedException {
        Runnable r = new Runnable() {
            public void run() {
                for (int i = 0; i < 100000; i++) {
                    counter++;
                }
            }
        };
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        if (counter != 200000) {
            System.out.println("Houston, we have a synchronization problem: counter should be 200000, but it is " + counter);
        }
    }
}

Running this program on my machine gives

Houston, we have a synchronization problem: counter should be 200000, but it is 198459
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
1

Here is the code. Sorry for the static, just wanted to save few lines of code, it does not affect the results:

public class Test
{
    public static int value;

    public static void main(String[] args) throws InterruptedException
    {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for(int i = 0; i < 50000; ++i)
                    ++value;
            }
        };

        List<Thread> threads = new ArrayList<Thread>();
        for(int j = 0; j < 2; ++j)
            threads.add(new Thread(r));
        for (Thread thread : threads)
            thread.start();
        for (Thread thread : threads)
            thread.join();

        System.out.println(value);
    }
}

This program can print anything between 50000 and 100000, but it never actually printed 100000 on my machine.

Now replace int with AtomicInteger and incrementAndGet() method. It will always print 100000 without big performance impact (it uses CPU CAS instructions, no Java synchronization).

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • Great example you provided. :) – EugeneP Aug 12 '11 at 08:23
  • Can you print the results you see as the first thread can complete before the second one starts. (See my answer) – Peter Lawrey Aug 12 '11 at 09:43
  • Interestingly enough, if you join the two loops (start(), join() in one block), you will get exactly the opposite effect, the counter value would be as expected. – EugeneP Aug 12 '11 at 10:59
  • *@Peter Lawrey*: on my computer (4-core desktop) these values are enough to show the problem (sometimes I got results only slightly higher than 50000...) – Tomasz Nurkiewicz Aug 12 '11 at 11:10
  • *@EugeneP*: nothing actually interesting in this case: by calling `start()` and `join()` alternately you are actually running only one thread at a time - spawning the first one and waiting until it finishes, then spawning a second... No concurrency - no race conditions. – Tomasz Nurkiewicz Aug 12 '11 at 11:12
1

You need to run the test for many iterations as ++ is quick and can run to completion before there is time for there to be a problem.

public static void main(String... args) throws InterruptedException {
    for (int nThreads = 1; nThreads <= 16; nThreads*=2)
        doThreadSafeTest(nThreads);
}

private static void doThreadSafeTest(final int nThreads) throws InterruptedException {
    final int count = 1000 * 1000 * 1000;

    ExecutorService es = Executors.newFixedThreadPool(nThreads);
    final int[] num = {0};
    for (int i = 0; i < nThreads; i++)
        es.submit(new Runnable() {
            public void run() {
                for (int j = 0; j < count; j += nThreads)
                    num[0]++;
            }
        });
    es.shutdown();
    es.awaitTermination(10, TimeUnit.SECONDS);
    System.out.printf("With %,d threads should total %,d but was %,d%n", nThreads, count, num[0]);
}

prints

With 1 threads should total 1,000,000,000 but was 1,000,000,000
With 2 threads should total 1,000,000,000 but was 501,493,584
With 4 threads should total 1,000,000,000 but was 319,482,716
With 8 threads should total 1,000,000,000 but was 261,092,117
With 16 threads should total 1,000,000,000 but was 202,145,371

with only 500K I got the following on a basic laptop. On a faster machine you can have a higher iteration count before a problem would be seen.

With 1 threads should total 500,000 but was 500,000
With 2 threads should total 500,000 but was 500,000
With 4 threads should total 500,000 but was 500,000
With 8 threads should total 500,000 but was 500,000
With 16 threads should total 500,000 but was 500,000
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130