【问题标题】:When I use volatile in project, why the code below shows different result? [duplicate]当我在项目中使用 volatile 时,为什么下面的代码显示不同的结果? [复制]
【发布时间】:2018-03-20 10:22:41
【问题描述】:

第一:

public class VolatileTest{
    public volatile int inc = 0;
    public void increase(){
        inc++;
    }

    public static void main(String[] args) {
        VolatileTest test = new VolatileTest();
        for(int i = 0 ; i < 2 ; i ++){
            new Thread(){
                public void run(){
                    for(int j = 0 ; j < 1000 ; j++)
                        test.increase();
                }
            }.start();
        } 
        while(Thread.activeCount() > 1)Thread.yield();
        System.out.println(test.inc);
    }
}

秒:

public class VolatileTest{
    public volatile int inc = 0;
    public void increase(){
        inc++;
    }

    public static void main(String[] args) {
        VolatileTest test = new VolatileTest();
        new Thread(){
            public void run(){
                for(int j = 0 ; j < 1000 ; j++)
                    test.increase();
            }
        }.start();
        new Thread(){
            public void run(){
                for(int j = 0 ; j < 1000 ; j++)
                    test.increase();
            }
        }.start();
        while(Thread.activeCount() > 1)Thread.yield();
        System.out.println(test.inc);
    }
}

第一个使用for而第二个没有,这是唯一的区别,但是第一个得到的结果小于2000,第二个得到的结果等于2000,为什么?

【问题讨论】:

  • 因为并发,试试public synchonized void increase(){inc++;}
  • while(Thread.activeCount() &gt; 1)Thread.yield(); 是什么意思?你想用它实现什么?
  • 我想它应该与while(!workIsDone) //don't terminate 循环相同,workIsDone 是一些布尔标志,表示所有线程都已完成工作。这可能不是最有效的方法,因为该方法仅返回对线程数的估计,这可能只是错误的,因为线程尚未完成初始化,因此它们不会出现。您可以只保存对线程的引用并进行 Thread.join() 调用,这更智能。

标签: java multithreading volatile


【解决方案1】:

考虑您在increase 方法中执行的此操作。您首先读取现有值,然后将其递增并写回。这里有几条指令,可以中断。获得小于 2000 的值的原因是由于竞争条件。使用关键字volatile 并不能保证原子性。为了保证原子性,您必须使用锁。试试这个。

private final Object lock = new Object();

public void increase() {
    synchronized (lock) {
        inc++;
    }

}

另一种选择是在此处使用AtomicInteger。所以你的代码现在看起来像这样。

public AtomicInteger inc = new AtomicInteger(0);
public void increase() {
    inc.incrementAndGet();
}

这也保证了顾名思义的原子性。

【讨论】:

  • 如果您不想使用自定义锁,您也可以使increase 方法本身同步以仅使用实例锁。
  • 是的,这是另一种选择。
【解决方案2】:

这只是巧合。两种变体都同样被破坏,但是第二个定义了两个不同的类做同样的事情,所以在启动第二个线程之前,这个额外的类必须被加载、验证和初始化。这种开销使第一个线程领先一步,从而提高了在第二个线程开始之前完全完成的机会。

所以竞争条件没有实现,但由于不能保证这种执行,它仍然是一个包含数据竞争可能性的损坏程序。在具有更快类加载/初始化或提前策略的环境中运行相同的程序可能会表现出与第一个变体相同的行为。

请注意,同样,不能保证第一个变体会丢失更新。启动第二个线程的速度可能仍然很慢,以至于第一个线程可以在没有数据竞争的情况下完成。即使两个线程都运行,系统的线程调度策略也可能会改变丢失更新的可能性。此外,整个循环可以优化为 1000 的单个增量,这不会与 volatile 变量的要求相矛盾,即使当前版本的 HotSpot JVM 没有这样做。

【讨论】:

  • 我通过在第二个代码的第二个线程中打印 test.inc 进行测试,然后得到 1000。这是正确的。
【解决方案3】:

jls 不保证第二次测试的结果2000,您可以让线程在增量之前的某个时间休眠以使其更容易“中断”:

public void increase(){
    try {
        Thread.sleep(20);
    } catch (Exception e) {

    }
    inc++;
}

你可能会得到:

1997
1999

或其他一些不可预测的结果。

volatile 可以保证对变量的更改总是对其他线程可见,但不能保证对该变量的操作是原子的。

假设i = 1,thread1 和thread2 可能同时读取1,并将其递增到2,然后再写回,导致错误结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-29
    • 2021-10-14
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    • 1970-01-01
    • 2019-03-07
    • 1970-01-01
    相关资源
    最近更新 更多