【问题标题】:Java program output - concurrentJava 程序输出 - 并发
【发布时间】:2011-03-29 20:00:30
【问题描述】:

这可能是一个愚蠢的问题,但是,这个程序的输出(它的方式)可以为零吗?

public class Test2{

    int a = 0;
    AtomicInteger b = new AtomicInteger();
    public static Test2  c = new Test2();

    public static void main(String[] args){

        Thread t1 = new Thread(new MyTest1());
        Thread t2 = new Thread (new Mytest2());

        t1.start();
        t2.start();
    }

}

class MyTest1 implements Runnable{

    public void run(){
        Test2.c.a = 1;
        Test2.c.b.compareAndSet(0,1);

    }
}

class Mytest2 implements Runnable{
    public void run(){
        int x = 0;

        if(Test2.c.b.get() == 1)
            x = Test2.c.a;

        System.out.println("Value of x = "+x);
    }

}

我问这个的原因是,虽然我使用的是 AtomicInteger,但 MyTest2 中的 if() 语句可能会先执行,然后 x 的值将为零..对吗?

还是我的想法不正确。

任何帮助将不胜感激。

【问题讨论】:

    标签: java concurrency


    【解决方案1】:

    这个程序的输出(它的方式)可以为零吗?

    是的,这是可能的。该代码不以任何方式保证t1 将在t2 完成之前完成。如果这是您的意图,您可能会发现 CountdownLatchCyclicBarrier 很有用。点击链接,他们的 javadocs 包含代码示例。


    也就是说,我宁愿将 AtomicInteger 的引用作为两个可运行对象的构造函数参数传递,而不是以静态方式访问它。

    【讨论】:

    • 谢谢……我明白了……我刚刚在某处看到了这段代码,cmets 读到“输出将为 1”——这就是我在这里发布它的原因。
    • 困扰我的是,如果您按以下顺序保留上述语句:t1.start(); t2.start();然后输出始终为 1,但如果您将顺序颠倒为: t2.start(); t1.start();然后输出总是 0。这真的很奇怪,因为即使在 100 次运行之后,我也不能让它为相同的顺序产生不同的输出。
    • 也许你有一个单核 CPU。在我的双核机器上,我看到0 大约每 5 次运行一次。无论如何,到目前为止发布的代码并不能保证线程以任何方式同步。这些线程在没有一些同步帮助的情况下过着自己的生活。对于另一个使用 CyclicBarrier 的代码示例,您可能会发现 this answer 很有用。
    • Achilles - 这是一般并发错误中最棘手的问题之一。通常情况下,您可以编写根据 JLS 模棱两可的代码,但在您的 JVM 和硬件的特定组合上产生预期的结果。您的 JVM 可以 产生替代输出,但实际上永远不会。当代码在其他地方运行时,由于其他环境因模棱两可而突然失败。
    【解决方案2】:

    只需稍作改动,您可以保证输出将是1

    public void run(){
        int x = 1;
    
        if(Test2.c.b.get() == 1)
            x = Test2.c.a;
    
        System.out.println("Value of x = "+x);
    }
    

    由于 volatile 读/写的顺序保证:

    • x 最初是 1
    • 唯一可以分配给x的其他值是Test2.c.a的值
    • 仅当先前从 Test2.c.b 读取了 1 时,Test2.c.a 才能复制到 x
    • 只有在将1 写入Test2.c.a 之后,1 才会写入Test2.c.b
    • 因为Test2.c.b 是易失性的,任何读取它的线程也会观察到之前写入Test2.c.b 的同一线程对所有变量(易失性或非易失性)的所有写入。这意味着t2 也可以看到1Test2.c.a 的写入
    • 如果测试失败(即读取Test2.c.b 返回的不是1),则x 保留其初始值1,因此输出仍为1

    我怀疑这就是这个程序的作者试图说明的内容,x 的默认值 0 是一个错字。

    【讨论】:

    • 很好的解释,但 IMO 原作者只是犯了一个思维错误,更改默认返回值不是解决方案,而是解决方法。那么,为此目的使用线程有什么意义呢?
    猜你喜欢
    • 2016-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-06
    相关资源
    最近更新 更多