【问题标题】:Why Volatile variable isn't used for Atomicity为什么 Volatile 变量不用于原子性
【发布时间】:2015-08-06 10:47:41
【问题描述】:

来自Javadocs

使用 volatile 变量可降低内存一致性的风险 错误,因为对 volatile 变量的任何写入都会建立 与后续读取相同的发生之前的关系 多变的。这意味着对 volatile 变量的更改总是 对其他线程可见。

当对 volatile 变量所做的更改始终对任何其他线程可见时,为什么在多个线程写入该变量的情况下不能使用 volatile 变量。为什么 volatile 仅用于一个线程正在写入或读取而另一个线程仅读取变量的情况?

如果更改总是对其他线程可见,那么假设如果线程 B 想要写入该变量,它将看到新值(由线程 A 更新)并更新它。而当线程A再次要写入时,它会再次看到线程B更新的值并写入它。问题出在哪里?

简而言之,我无法理解这一点。

如果两个线程都在读取和写入共享变量,那么 使用 volatile 关键字是不够的。你需要使用 同步在这种情况下,以保证读写 变量是原子的。

【问题讨论】:

  • 没有一个线程必须是“仅”读取或写入。 reader 和 writer 的角色可以是“流利的”,并且可以有两个以上的线程参与这种交换。
  • 如果一个线程写入一个值到一个变量,它什么时候看到(读取)一个值?请澄清你的最后一段。
  • @SotiriosDelimanolis,它不会看到,但会遇到它的更新值。所以它会写入更新的值。
  • @Thilo,这个答案说应该读一读。 stackoverflow.com/a/17748517/3292108
  • 啊,那请看answer given by ruakh。该语句指的是一种模式,其中读取和写入都应作为单个外部可见操作以原子方式完成。

标签: java multithreading


【解决方案1】:

volatile 可以很好地用于许多用途,但也有很多用途不能。例如,假设您有这样一个字段:

private volatile int i;

还有两个同时运行 ++this.i 的线程:读取 this.i 然后写入它。

问题在于++this.i 是一个易失性读取,然后是一个完全独立的易失性写入。在读取和写入之间可能发生了许多事情。特别是,您可能会遇到两个线程在任一线程写入之前读取i 的情况。最终结果是i 的值仅增加了 1,即使两个单独的线程都增加了它。

AtomicInteger(和其他原子)通过允许您在单个原子(≈易失性)步骤中同时读取和写入来解决此类问题。 (他们通过使用比较和交换指令来执行此操作,该指令仅在读取的值仍然是当前值时执行写入。增量和获取方法只是运行一个循环,重试此操作直到写入实际成功。 )

【讨论】:

  • 很好的例子。问题不在于一个线程在读取该字段时会以某种方式错过另一个线程的更新(易失性确保它有效)。问题是该字段可以由另一个线程在您读取它之后更改,因此您不能真正依赖于您刚刚读取的内容并且需要采取额外的同步步骤(将字段锁定一段时间)。
  • @ruakh,假设如果两个线程同时来写入变量 i。那么在这种情况下,写入不会总是线程安全的吗?因为其他线程总是会写入最新的值。
  • @Diffy 如果您正在编写一个新值,为什么要关心最新值?
  • @SotiriosDelimanolis 因为如果线程 A 将 i 的值增加到 1,那么我希望线程 B 将值从 1 更新为 2。这就是我关心更新值的原因。
  • @Diffy 所以您需要读取该值,可能检查它是否是您想要的值,然后写入一个新值。这就是++ 所做的。无论volatile 是什么,它都不是作为一个组是原子的。
【解决方案2】:

想想“原子性”是什么意思。这意味着就其他线程而言,在一个线程中发生的两个或多个操作似乎作为一个原子单元发生。

如果我声明一些volatile int foobar,并编写代码对其执行一些操作,编译器如何知道哪些这些操作应该是原子单元?

当您编写 synchronized 块时,原子单元就是您放入块中的任何内容。

【讨论】:

    猜你喜欢
    • 2012-10-03
    • 1970-01-01
    • 1970-01-01
    • 2016-11-05
    • 2016-11-29
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    • 2017-02-18
    相关资源
    最近更新 更多