Java - 之前发生 - 易变

时间:2014-01-29 10:39:04

标签: java multithreading java-memory-model happens-before

我有以下代码

class VolatileCount {
    volatile int count;
    Object lock = new Object();

    public void increment() {

        synchronized (lock) {
            count = count + 1;
        }
        System.out.print(" " + count);
    }

}

如果我从多个线程调用increment()相同的对象,我会得到以下输出(可能在您的机器上有所不同)

2 3 2 5 4 8 8 6 11 13 10 9 15 14 12 20 19

看着重复的数字,我认为发生 - 之前似乎被打破了,因为考虑前三个数字(2 3 2),如果一个线程看到3,则发生增量,因为变量是volatile,其值应为3或更大,但在任何线程中都不能为2 但是,打印线似乎已在这里重新排序,重新排序该行是否正确?我在这里错过了什么?我在JDK 7(Eclipse)上运行

4 个答案:

答案 0 :(得分:7)

<强>更新

因为您似乎想要解释“2 3 2”的具体情况

  • X递增i(现在为1)
  • Y递增i(现在为2)
  • X读取i(X加载2)
  • Y读取i(Y加载2)
  • Y打印先前加载的值(打印2)
  • Z递增i,读取i,打印i(打印3)
  • X打印先前加载的值(打印2)

重点是:System.out.print(" " + count)不是原子的。执行易失性读取之后和打印值之前,线程可以被抢占。

如果您想防止打印重复值,您必须执行锁定中的易失性读取

public void increment() {
    int localCount;
    synchronized (lock) {
        count = count + 1;
        localCount = count; // volatile load
    }
    System.out.print(" " + localCount);
}

但这不会阻止值无序打印。为了按顺序打印它们,没有重复,您还必须将print移动到锁中。

旧答案

print语句在锁外。 考虑在 System.out.print(" " + count)内运行的代码。

  • 线程X递增i
  • 线程X评估print参数并对count变量执行易失性读取,并加载值2
  • 线程X被线程Y抢占,线程Y递增i
  • 线程Y加载i(现在是3),调用运行完成的print方法。
  • 线程Y被抢占,线程X现在运行print完成,打印2。

这会使数字显示乱序,例如“3 2 4”。

在以下情况下也可能会重复某些数字:

  • 线程X递增i(现在为2)
  • 线程Y增加i(现在为3)
  • 线程X打印i(即3)
  • 线程Y打印i(即3)

答案 1 :(得分:2)

考虑2个主题。第一个线程获得锁定,递增计数到1并释放锁定。第二个线程获得锁定,增量计数为2,释放锁定,打印2,然后第一个线程获取count(现在为2)值以打印它。在这种情况下,您将获得2 2

特别针对您的示例:

  1. 线程A增量计数为1
  2. 线程B将其增加到2
  3. 线程A获取并打印值(2)
  4. 线程B获得值(2)
  5. 主题C将其修改为3并将其打印
  6. 线程B打印值(仍然是2,因为它是它得到的)
  7. 你有2 3 2 等...

答案 2 :(得分:1)

问题可能出在System.out.print多线程上 检查this discussion 并尝试从命令行运行它,因为您的IDE可能会缓冲输出

如果您将打印件放入锁内,则应解决问题

答案 3 :(得分:0)

您的打印输出位于受保护区域之外。这意味着您可能会在增量和打印之间的任何时间被中断,包括在获取计数之后但在将其发送到System.out之前。如果你在同步块中移动它,我相信你会看到你期望的行为。

易失性不是同步的替代品。它只是警告编译器该值可能随时发生变化,因此应在每次引用时取出而不是优化出来。