arjacsoh
arjacsoh

Reputation: 9242

Strange multithreading behavior

I am trying to execute three methods on a single instance in three different Threads in specific order. That is methodA should be called before methodB and methodB before methodC.

I have tried initially a not sophisticated approach with the code:

public void testFoo()
{
    Foo foo = new Foo();
    ExecutorService service = Executors.newFixedThreadPool(3);
    service.submit(() -> foo.callMethodB());
    service.submit(() -> foo.callMethodC());
    service.submit(() -> foo.callMethodA());
    service.shutdown();
}

class Foo 
{

    boolean  methodAcompleted;
    boolean  methodBcompleted;

    void callMethodA() 
    {
        this.methodA();
        methodAcompleted = true;
    }

    void callMethodB() 
    {
        while (true) 
        {
            if (methodAcompleted)
            {
                this.methodB();
                methodBcompleted = true;
                break;
            }
        }
    }

    void callMethodC() 
    {
        while (true) 
        {
            if (methodBcompleted) 
            {
                this.methodC();
                break;
            }
        }
    }

    void methodA()
    {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method A completed!");
    }

    void methodB()
    {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method B completed!");
    }

    void methodC()
    {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method C completed!");
    }
}

I came into the following odd observations:

1) Executing the code as above only "Method A completed!" is printed out. Even if I change the boolean variables to static I get the same result. 2) If I change the boolean variables to volatile then all statements are printed in the right order:

"Method A completed!"
"Method B completed!"
"Method C completed!"

As far as I know this is expected, since it is not guaranteed (or impossible?) that a Thread is informed of change by another Thread action on the same instance. Is it true even if the changes are executed on static variables?

3) It becomes strange when I remove the delay Thread.sleep() from the methods having them run as:

void methodA()
{
        System.out.println("Method A completed!");
}

void methodB()
{
        System.out.println("Method B completed!");
}

void methodC()
{
        System.out.println("Method C completed!");
}

In this case the statements are printed always in the right order like in case 2:

"Method A completed!"
"Method B completed!"
"Method C completed!"

Can/should a Thread see the changes from another Thread acting on the same not volatile variables? What is the explanation on the different behavior/result between case 1 and case 3?

Update:

I think if the above approach were indispensable, the best solution is to use AtomicBoolean instead of primitives as:

class Foo {
    AtomicBoolean methodAcompleted = new AtomicBoolean(false);
    AtomicBoolean methodBcompleted = new AtomicBoolean(false); 

    ...
}

I am persuaded that the purpose of atomic types is to guarantee consistent behavior in those cases.

Upvotes: 1

Views: 71

Answers (1)

Peter Lawrey
Peter Lawrey

Reputation: 533670

You need a memory barrier to prevent the JIT inlining the value of the boolean field.

The JIT can detect you don't change your flag in the current thread and can inline it, preventing you from ever seeing the change.

If you add volatile or a synchronized method (even an empty synchronised block) it prevents this optimisation. Note: System.out.println is synchronized.

Can/should a Thread see the changes from another Thread acting on the same not volatile variables?

It can and will see changes for other types as this optimisation may not apply to other type (Depending on your JVM implementation)

Also if you change the flag before the JIT has optimised the code, you will see the change.

I wrote an article on this a few years ago which has more details http://vanillajava.blogspot.co.uk/2012/01/demonstrating-when-volatile-is-required.html

Upvotes: 2

Related Questions