Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the volatile Happens-Before order for Instruction Reordering fails?

I hava following code to test volatile. bEnd and nCount are defined volatile.

nCount = 0, bEnd = false

The Writer thread will set

nCount = 100, bEnd = true

The Reader thread read these viriables and print them. Base on the Java Happens-before order, in my opinion, volatile ensures nCount = 100 when bEnd = true. But sometimes the program print this:

main thread done.
thread Reader running ...
thread Writer running ...
SharedData nCount = 0, bEnd = false
thread Writer bEnd = true
thread Reader nCount = 0, bEnd = true
thread Reader nCount = 100, bEnd = true
thread Reader nCount = 100, bEnd = true
thread Reader done.

How can the Reader get "nCount = 0, bEnd = true" ???

The following code running on windows10, jdk1.8.0_131

public class HappensBeforeWithVolatile {

    public static void main(String[] args) {

        Thread threadWriter = new Thread(new Writer());
        Thread threadReader = new Thread(new Reader());
        threadWriter.start();
        threadReader.start();

        System.out.println("main thread done.");
    }
}

class Writer implements Runnable {

    @Override
    public void run() {
        System.out.println("thread Writer running ...");
        SharedData.nCount = 100;
//        System.out.println("thread Writer nCount = 100");
        SharedData.bEnd = true;
        System.out.println("thread Writer bEnd = true");
    }
}

class Reader implements Runnable {

    @Override
    public void run() {
        System.out.println("thread Reader running ...");
        System.out.println("thread Reader nCount = " + SharedData.nCount + ", bEnd = " + SharedData.bEnd);
        System.out.println("thread Reader nCount = " + SharedData.nCount + ", bEnd = " + SharedData.bEnd);
        if (SharedData.nCount == 0 && SharedData.bEnd) {
            System.out.println("thread Reader CODE REORDER !!!");
        }
        System.out.println("thread Reader nCount = " + SharedData.nCount + ", bEnd = " + SharedData.bEnd);
        System.out.println("thread Reader done.");
    }
}

class SharedData {
    volatile public static boolean bEnd = false;
    volatile public static int nCount = 0;

    static {
        System.out.println("SharedData nCount = " + nCount + ", bEnd = " + bEnd);
    }
}
like image 693
x_iunknown Avatar asked Aug 08 '18 08:08

x_iunknown


2 Answers

volatile ensures nCount = 100 when bEnd = true

Technologically, yes. But the reader did not read them atomically. So it might print nCount = 0 and bEnd = true.

Here is an example:

  1. Reader reads nCount 0
  2. Wirter writes nCount = 100
  3. Wirter writes bEnd = true
  4. Writer prints thread Writer bEnd = true
  5. Reader reads bEnd true
like image 183
xingbin Avatar answered Oct 17 '22 01:10

xingbin


Your entire example is a bit flawed btw. To test that happens-before, you need to test subsequent actions, that is make bEnd volatile and nCount not a volatile, then simplify your example as:

static class Reader implements Runnable {
    @Override
    public void run() {

        while (true) {
            if (SharedData.bEnd) {
                System.out.println(SharedData.nCount);
                break;
            } else {
                System.out.println("Not yet seen as true");
                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
            }
        }
    }
}

static class Writer implements Runnable {
    @Override
    public void run() {
        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
        SharedData.nCount = 100;
        SharedData.bEnd = true;

    }
}

That will output 100, always (in this case at least). The correct explanation would be that if a Reader Thread sees the update of the Write thread of a volatile variable, it will see everything that was done before that, thus 100.

like image 1
Eugene Avatar answered Oct 17 '22 02:10

Eugene