简体   繁体   中英

Understanding Java Thread Interference

I'm learning Java, coming from a python background, and trying to understand thread interference, starting with the code & explanation in this page: http://docs.oracle.com/javase/tutorial/essential/concurrency/interfere.html

To reproduce interference, I have another class that starts three threads, each randomly calling either increment or decrements 10 times.

I expect, with 3 threads & 30 increments or decrements, some would overlap, and thus the final Counter value would not equal the (# increments) - (# decrements) .

But every time I run the code and analyze the resulting output, I find the final value to equal (# increments) - (# decrements) . Although its possible that after 5 runs, I somehow did not get any interference, its much more likely that I misunderstood the interference effect or inadvertently implemented code that avoids interference.

Here's my code:

// file: CounterThreads.java
public class CounterThreads {
    private static class CounterThread implements Runnable {
        private Counter c;

        CounterThread(Counter c)
        {
            this.c = c;
        }

        public void run()
        {
            String threadName = Thread.currentThread().getName();
            for (int i=0; i<10; i++) {
                try {

                    if (((int)(Math.random() * 10) % 2) == 0) {
                        System.out.format("%s - Decrementing...\n", threadName);
                        c.decrement();
                    } else {
                        System.out.format("%s - Incrementing...\n", threadName);
                        c.increment();
                    }
                    System.out.format("%s - The internal counter is at %s\n", threadName, c.value());
                    Thread.sleep(1000);

                } catch (InterruptedException e) {
                    System.out.format("Thread %s interrupted\n", threadName);
                }
            }
        }
    }

    public static void main(String[] args)
    {
        Counter c = new Counter();
        for (int  i=0; i<3; i++) {
            Thread t = new Thread(new CounterThread(c));
            System.out.format("Starting Thread: %s\n", t.getName());
            t.start();
        }
    }
}

The file Counter.java contains code copied from the oracle documentation above, reproduced here for convenience

// file: Counter.java
public class Counter {
    private int c = 0;

    void increment ()
    {
        c++;
    }

    void decrement()
    {
        c--;
    }

    int value()
    {
        return c;
    }
}

To reproduce, you need to maximize the probability to increment and/or decrement your counter concurrently (NB: it is not an easy task since incrementing/decrementing a counter is a very fast operation), which is not the case of your current code because:

  1. You don't use any mechanism to synchronize the threads before incrementing/decrementing your counter.
  2. You print your messages in the standard output stream too often and at the wrong place which is a problem when you know that a PrintStream is thread-safe and uses an intrinsic lock to prevent concurrent accesses which reduces the probability to increment and/or decrement your counter concurrently.
  3. You add a useless long sleep which once again reduces the probability to get concurrent modifications of your counter.
  4. You don't use as many threads as you can.

So, your code should be rewritten a little bit to fix the previous problems.

To fix #1, you can use a CyclicBarrier to make sure that all your threads reach the same barrier point (located just before incrementing/decrementing your counter) before going any further.

To fix #2, I would recommend to keep only one message after having incremented/decremented your counter.

To fix #3, I would simply remove it as it is useless anyway.

To fix #4, I would use Runtime.getRuntime().availableProcessors() as amount of threads to use since it will use as many processors as you have on your local machine which should be enough for such kind of task.

So the final code could then be:

Counter

public class Counter {
    private final CyclicBarrier barrier;
    private int c;

    public Counter(int threads) {
        this.barrier = new CyclicBarrier(threads);
    }

    void await() throws BrokenBarrierException, InterruptedException {
        barrier.await();
    }
    ...
}

The main method

public static void main(String[] args) {
    int threads = Runtime.getRuntime().availableProcessors();
    Counter c = new Counter(threads);
    for (int  i=0; i<threads; i++) {
        ...
    }
}

The for loop of the run method

try {
    // Boolean used to know if the counter has been decremented or not
    // It has been moved before the await to avoid doing anything before
    // incrementing/decrementing the counter
    boolean decrementing = (int)(Math.random() * 10) % 2 == 0;
    // Wait until all threads reach this point
    c.await();
    if (decrementing) {
        c.decrement();
    } else {
        c.increment();
    }
    // Print the message
    System.out.format(
        "%s - The internal counter is at %d %s\n", 
        threadName, c.value(), decrementing ? "Decrementing" : "Incrementing"
    );

} catch (Exception e) {
    System.out.format("Thread %s in error\n", threadName);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM