4

我有以下代码

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

查看重复的数字,我认为happens-before似乎被破坏了,因为考虑到前三个数字(2 3 2),如果线程看到 3 ,则增量已经发生,并且由于变量是易失性的,因此它的值应该是 3 或更多但是在任何线程中不能为 2。
但是,这里的打印行似乎已经重新排序,重新排序该行是否正确?我在这里想念什么?我在 JDK 7 (Eclipse) 上运行

4

4 回答 4

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)不是原子的。在执行易失性读取之后和打印值之前,线程可以被抢占。

如果要防止打印重复值,则必须在锁内执行 volatile 读取:

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

不过,这不会阻止值被乱序打印。为了按顺序打印它们,没有重复,您还必须将它们print移入锁中。

旧答案

打印语句在锁之外。考虑里面 System.out.print(" " + count)运行的代码。

  • 线程 X 递增i
  • 线程 X 评估print参数并对变量执行 volatile 读取count,并加载 value 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)
于 2014-01-29T10:42:40.097 回答
2

考虑2个线程。第一个线程获得锁,增加计数1并释放锁。第二个线程获得锁,将 count 增加到2,释放锁,打印 2,然后第一个线程获取 count 的值(现在2)打印它。在这种情况下,你会得到2 2

特别是你的例子:

  1. 线程 A 将 count 增加到 1
  2. 线程 B 将其增加到 2
  3. 线程 A 获取并打印值 (2)
  4. 线程 B 获取值 (2)
  5. 线程 C 将其增加到 3 并打印它
  6. 线程 B 打印值(仍然是 2,因为这是它得到的)

你有2 3 2 等等...

于 2014-01-29T10:59:09.163 回答
1

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

如果你把印刷品放在锁里,应该可以解决问题

于 2014-01-29T10:54:19.243 回答
0

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

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

于 2014-01-29T10:47:57.420 回答