【问题标题】:Strange multithreading behavior奇怪的多线程行为
【发布时间】:2016-10-16 09:56:52
【问题描述】:

我正在尝试以特定顺序以三个不同的Threads 在单个实例上执行三个方法。也就是说methodA 应该在methodB 之前调用,methodBmethodC 之前调用。

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

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 的目的是保证在这些情况下的行为一致。

【问题讨论】:

    标签: java multithreading volatile


    【解决方案1】:

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

    JIT 可以检测到您没有更改当前线程中的标志并可以内联它,从而阻止您看到更改。

    如果您添加 volatilesynchronized 方法(甚至是空的同步块),它会阻止此优化。注意:System.out.println 是同步的。

    一个线程可以/应该看到另一个线程对相同的非易失性变量起作用的变化吗?

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

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

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

    【讨论】:

      猜你喜欢
      • 2015-01-07
      • 2017-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多