TMOTTM
TMOTTM

Reputation: 3381

Should volatile variables in Java not be visible on second thread?

I have a small example application with two threads A and B. Both spend some CPU cycles before each setting a value on one volatile variable (A sets the value for x, B sets the value for y), then B also prints out the two variables. When this game is repeated a number of times then sometimes the value of x is visible in B but sometimes not (even if the x and y are both volatile). Why is that?

public class Vol2 {

    public static volatile int x = 0;
    public static volatile int y = 0;

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 50; i++) {

            x = 0;
            y = 0;


            Thread t1 = new Thread(() -> {
                doWork();
                x = 5;
            });

            Thread t2 = new Thread(() -> {
                doWork();
                y = 6;
                System.out.println("x: " + x + ", y: " + y);
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

        }
    }

    public static void doWork() {
        int s = 0;
        for (int i = 0; i < 1_000_000; i++) {
            s += i;
        }
    }
}

Upvotes: 1

Views: 68

Answers (3)

Eugene
Eugene

Reputation: 120858

There are subtle problems with your example. First is that doWork might be entirely useless, it does not have any side-effects and JIT can eliminate that entirely. Think about it: since that loop is purely a local thing that no one sees, why do it in the first place?

Then, you have somehow a wrong understanding about Thread::start, which this answer already explains to you. t1.start(); schedules a thread to be started, it does not mean that it will finish (or even start) before t2.start();.

Then, at least IMHO, you are using the wrong tool for the job. You need jcstress, a tool tailored by experts for this kind of things. Here is how your code is going to look like using it:

@JCStressTest
@State
@Outcome(id = "0, 0", expect = Expect.FORBIDDEN, desc = "can not happen")
@Outcome(id = "0, 6", expect = Expect.ACCEPTABLE, desc = "writerY only")
@Outcome(id = "5, 6", expect = Expect.ACCEPTABLE, desc = "both")
public class TwoThreads {


    volatile int x = 0;
    volatile int y = 0;

    @Actor
    void writerX(II_Result r) {
        x = 5;
    }

    @Actor
    void writerY(II_Result r) {
        y = 6;
        r.r1 = x;
        r.r2 = y;
    }

}

How to run that and what it means - is an exercise to you, but the main point is that running this will only give you two possible outcomes:

5, 6 --> meaning writerX finished its work before writerY

or translated to your code, t1 before t2.

0, 6 --> meaning writerY finished its work before writerX

and as such that x = 5 by writerX was lost and never recorded. Or, translated to your code, t2 finished before t1.

Upvotes: 0

WJS
WJS

Reputation: 40044

Try it like this. Assign the variable and then do some work. That gives each thread a chance to set the value. Threads always take a finite amount of time to start and start asynchronously, so events at the beginning can't be predicted.

public class Vol2 {

    public static volatile int x = 0;
    public static volatile int y = 0;

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 50; i++) {

            x = 0;
            y = 0;


            Thread t1 = new Thread(() -> {
                x = 5;
                doWork();
            });

            Thread t2 = new Thread(() -> {
                y = 6;
                doWork();
                System.out.println("x: " + x + ", y: " + y);
            });

            t1.start();
            t2.start();
            t1.join();
            t2.join();

        }
    }

    public static void doWork() {
        int s = 0;
       try {
           Thread.sleep(10);
       } catch (InterruptedException ie){}
    }
}

Upvotes: 0

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147164

Threads t1 and t2 may execute at the same time. There's no guarantee that t1 will have assigned x before t2 has assigned y and read x.

Upvotes: 1

Related Questions