0

I've been thinking about it for almost the whole afternoon. Why this program its volatile variable almost couldn't approach 10000. Here is the code:

public class TestModifyVolatile {
    volatile int count = 0;
    void m(){
        count++;
    }
    public static void main(String[] args) {
        List<Thread> threads = new ArrayList<>();
        TestModifyVolatile t = new TestModifyVolatile();
        for (int i=0;i<10000;i++){
            threads.add(new Thread(t::m,"t_"+i));
        }
        threads.parallelStream().forEach(e->e.start());
        threads.forEach(o->{
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }
}

Most of the result is 9996 9998... Assume thread 1 starts first, thread 1 made variable count turned to 1. In the process described above. Maybe another thread started at the same time as thread 1 that read variable count is also 0 and wrote back 1? That's my guess but i can not prove it

Gray
  • 115,027
  • 24
  • 293
  • 354
KevinWyen
  • 11
  • 1
  • 2
    This is possible if `count++` is not an atomic increment, which probably isn't. If it is implemented as `count = count+1`, the assignment to `count` by one thread may overwrite the assignment made to count by another thread. Volatile only ensures updates are visible to all threads, it doesn't ensure atomic updates involving multiple read-writes – Burak Serdar Jun 13 '21 at 04:07
  • Burak Serdar already the correct answer. You are suffering from a race condition. – pveentjer Jun 13 '21 at 04:35

1 Answers1

5

The increment operation count++ is not an atomic operation, even though the count field is variable. In fact, it performs a volatile read followed by a volatile write. Between the read and the subsequent write, some other thread can read the (initial) value of count. So you can have the following interleaving:

   count is 100
   thread A:  read count -> 100
   thread B:  read count -> 100
   thread A:  write 101 to count -> 101
   thread B:  write 101 to count -> 101

and you have lost an increment. (

Note that there is nothing in the current JLS that states specifically that volatile increment is non-atomic. However, it follows from what the JLS 17.4 specifies for the memory visibility properties of volatile fields.

It is not possible to implement a reliable (thread-safe) counter using a single2 volatile field and no additional synchronization. Use AtomicInteger instead.


1 - It might be possible with two. I'm not sure.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • It would be good to mention race conditions @Stephen. – Gray Jun 14 '21 at 22:23
  • Well ... obviously ... the interleaving shown above is an example of a race condition. – Stephen C Jun 14 '21 at 22:36
  • I'm not sure it's obvious to the poster. – Gray Jun 14 '21 at 22:49
  • Contract of the volatile is that Thread B again needs to fetch from main memory. in which Thread B must use 101 from main memory instead of 100 from cpu registers – Shyam Jun 15 '21 at 03:48
  • The point is that at the point where B fetches from the volatile field, the field (i.e. memory!) still contains 100. So that is what thread B sees. – Stephen C Jun 15 '21 at 04:08