奇怪的多线程行为

时间:2016-10-16 09:56:53

标签: java multithreading volatile

我尝试按特定顺序在三个不同的Threads中的单个实例上执行三个方法。那个methodA应该在methodBmethodB methodC之前调用。

我最初尝试过一种不复杂的代码方法:

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!");
    }
}

我进入了以下奇怪的观察:

1)仅打印"Method A completed!"执行上述代码。即使我将布尔变量更改为static,我也会得到相同的结果。 2)如果我将布尔变量更改为volatile,则所有语句都按正确的顺序打印:

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

据我所知,这是预期的,因为无法保证(或不可能?)Thread通知同一实例上的另一个Thread操作的更改。即使在static个变量上执行了更改,它也是如此吗?

3)当我从运行它们的方法中删除延迟Thread.sleep()时会变得很奇怪:

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

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

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

在这种情况下,语句总是以正确的顺序打印,如案例2:

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

Thread可以/应该看到另一个Thread对同一个非volatile变量的更改吗? case 1case 3之间不同行为/结果的解释是什么?

更新

我认为如果上述方法不可或缺,最好的解决方案是使用AtomicBoolean代替原语:

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

    ...
}

我被说服atomic types的目的是保证在这些情况下的一致行为。

1 个答案:

答案 0 :(得分:2)

您需要一个内存屏障来阻止JIT内联boolean字段的值。

JIT可以检测到你没有在当前线程中更改你的标志并且可以内联它,防止你看到变化。

如果添加volatilesynchronized方法(即使是空的同步块),也会阻止此优化。注意:System.out.println已同步。

  

线程可以/应该看到来自另一个线程的更改是否作用于相同的非易失性变量?

它可以并且将看到其他类型的更改,因为此优化可能不适用于其他类型(取决于您的JVM实现)

此外,如果您在JIT优化代码之前更改了标志,您将看到更改。

几年前我写了一篇关于此的文章,其中有更多细节http://vanillajava.blogspot.co.uk/2012/01/demonstrating-when-volatile-is-required.html