Code to simulate race condition in Java thread

后端 未结 4 2068
日久生厌
日久生厌 2021-01-05 07:15

I am new to Java multithreading. I am learning the concept of race condition.

Based on the Oracle document

http://docs.oracle.com/javase/tutorial/essential/

4条回答
  •  孤城傲影
    2021-01-05 07:31

    In order to have a race between two threads, there must be shared state between those two threads and interaction (reading and writing) to that state must occur outside of a mutualy exclusive block (aka syncronized). Reading, incrementing and then writing back to a volatile field outside of a synchronized block is a great example of this.

    For example, consider this situation documented on this blog.

    Both thread A and B could read the counter before any modification occurs. They then both increment, and they then both write. The end result will then be 18, and not 19. For it to have been 19, we would have needed thread B to read the counter AFTER thread A had written to the counter. Which, can happen sometimes. That is why it is called a race.

    enter image description here

    To reliably achieve this kind of race, change your test code above to create the counter outside of the threads and then pass it in to them via their constructors.

    The second problem that you have is that the window for the operations to overlap is very fine, and given that starting a thread has, in comparison a lot of over head then the chances of these three threads over lapping at just the right time is very low. Thus to increase their odds, you should repeat the runs in a tight loop.

    The following code demonstrates the two concepts above. The changes made have been:

    1. renamed classes to make their use a little clearer
    2. shared the state of MyCounter between the two threads
    3. tight loop within each thread, calling increment 1,000,000 times
    4. the main thread now blocks using join() waiting for the two threads to complete, this replaces the Thread.sleep that you had earlier
    5. the counter value c in MyCounter is now volatile; this tells the JVM to always go out to shared memory for the value and not to optimise by keeping it within a register between encounters. to make the race much worse, take volatile off and see what happens :)
    6. the main loop then finishes by printing out the value of the counter, which should be 2,000,000. but it will not be due to the race that is going on over the volatile counter.

    .

    public class CounterTest {    
        public static void main(String[] args) throws InterruptedException {   
            MyCounter counter = new MyCounter();
    
            Thread thread1 = new Thread(new CounterIncRunnable(counter));
            thread1.setName("add thread");
            thread1.start();
    
            Thread thread2 = new Thread(new CounterIncRunnable(counter));
            thread2.setName("add thread2");
            thread2.start();
    
            thread1.join();
            thread2.join();
    
            System.out.println(counter.value());
        }    
    }
    
    
    class CounterIncRunnable implements Runnable {
        private MyCounter counter;
    
        public CounterIncRunnable(MyCounter counter) {
            this.counter = counter;
        }
    
        public void run() {
            for ( int i=0; i<1000000; i++ ) {
                counter.increment();
            }
        }
    }
    
    
    class MyCounter {
        private volatile int c = 0;
    
        public  void increment() {
            c++;
        }
    
        public   void decrement() {
            c--;
        }
    
        public  int value() {
            return c;
        }    
    }
    

    Finally, just for fun; add synchronized to the increment method of MyCounter and then rerun. The race condition will disappear, and now the program will correctly print 2000000. This is because every call to increment will now only allow one thread in to the shared method at a time. Thus serializing each access to the shared variable c, and putting an end to the race.

提交回复
热议问题