【问题标题】:Atomicity of Reads and Writes for Variables in JavaJava中变量读写的原子性
【发布时间】:2017-03-10 17:00:21
【问题描述】:

这是对另一个 question of mine 的跟进。

@templatetypedef 回答了这个问题(赞赏),他在回答中写道:

请注意 - 原子性并不意味着“所有其他线程都将被阻塞 直到值准备好。这意味着所有其他线程要么会看到 纯粹在操作完成之前或纯粹之后的状态 操作已完成,但没有其他内容。

我对此感到困惑,原因如下:

上面写着here

原子动作不能交错,因此可以放心使用 线程干扰。

我由此推断,与他所写的相矛盾。

如果我们有2个int变量i1和i2,我们做原子操作i1=i2;,这个操作由threadX执行。

如果原子操作不能像上面指出的那样交错,这意味着在这个原子操作期间(由 threadX 执行),不允许其他 threadY 访问(读取或写入)同一个变量 i2,因此,没有其他threadY 允许在原子操作期间访问同一个变量,因此确实存在某种形式的阻塞。

我做对了吗?

谢谢...

【问题讨论】:

  • 不,这意味着 threadY 将看到 all 的分配,或 none 的分配......它不会看到“ i2 的几位,以及 i1 先前值的几位。
  • @Jon Skeet,所以如果你说的是真的,那么这句话:“原子动作不能交错,所以可以使用它们而不用担心线程干扰。”是 flase,因为在原子操作 i1=i2 期间,threadX 和 threadY 可能会在相同的数据(本例中为 i2)上交错。我明白你的观点 threadY 将始终在原子操作之前或之后读取值,但如果在原子操作期间允许 threadY 读取 i2 i1=i2;然后threadX和threadY在i2上交错,但是根据Oracle教程是不可能的,我错过了什么吗?
  • Reading i2(或者,通常是来自不同 CPU 缓存的 i2 的单独副本)不能构成交错,因为即使它发生在同一个挂钟时间。 在同一挂钟时间在不同线程中写入 i2 不构成交错,如果最终结果是“i1=i2”获得完整的旧 i2 值或完整的新 i2 值,因为它与另一个线程在“i1=i2”之前或之后执行操作的行为一致,因此这不是在原子操作中间交错,它只会导致不同的顺序。
  • 关于“...可以使用而不必担心线程干扰...”的引用并不能说明全部情况。当我们谈论“线程安全”时,我们必须在架构的每一层谈论它。由于此处已经讨论过的原因,对int 变量的读取和写入是线程安全的,但这不会自动使使用 int 变量的算法成为线程安全的。而且,如果您采取必要的步骤来确保算法的安全性,那并不能确保调用您的算法的代码的安全性,以此类推。

标签: java multithreading atomic


【解决方案1】:

据我所知,没有原子i1 = i2 操作。您可以原子地读取一个 int,也可以原子地写入一个 int,但是您不能在同步的同一操作中同时执行这两种操作。所以i1 = i2 是两个不同的原子操作,先读后写。您可以保证没有任何内容会交叉读取操作,因此您在阅读时不会看到对i2 的部分更新,并且您可以保证不会交叉写入到i1,但不能保证不会发生任何事情在这两个原子操作之间。

假设线程 t1 要做:

i2 = 10
i1 = i2

线程 t2 要做的事情:

i1 = 7
i2 = 18
System.out.println(i1)

您可以保证 t1 最终会将 10 或 18 分配给 i1 但您不知道是哪个。但是,您可以保证它不能是任何其他值,因为 i2 的读取和对 i1 的写入是原子的,因此在修改 i2 时您最终无法看到 i2 的某些位。同样, t2 保证打印 10、18 或 7 并且不能打印其他任何内容。但是,如果没有同步,就无法知道最终会打印这 3 个值中的哪一个。

【讨论】:

    【解决方案2】:

    ... 这意味着在此原子操作期间(由 threadX 执行),不允许其他 threadY 访问(用于读取或写入)相同的变量 i2,因此,不允许其他 threadY 访问相同的变量原子操作期间的变量,因此确实存在某种形式的阻塞。

    不,你没有做对。

    原子操作意味着线程无法看到处于部分状态的值。分配是原子的,具体取决于运行 JVM 的底层架构以及 i1i2 的数据大小。我相信 Java 说 int 字段是原子分配的,但 long(和 double)可能不是,因为它可能需要 CPU 进行多次操作。

    原子操作不能交错,因此可以使用它们而不必担心线程干扰。

    这是对的。如果i1 为1,i2 为2,threadX 执行赋值,那么任何其他线程将看到i1 的值是1(旧值)或2(新值)。 ThreadY 不会看到它介于 1 或 2 之间,因为即使多个线程正在更新 i1 的值,该分配也是原子的。

    但真正令人困惑的是这里有两个概念:原子性和内存同步。对于线程,每个 CPU 都有自己的内存缓存,因此首先对本地内存进行内存操作,然后将这些更改写入主内存。一个线程可能会在其本地缓存内存中看到i1 的旧副本,即使另一个线程已经更新了主内存。更糟糕的是,当两个线程在它们的本地内存中更新了i1 的值,并且根据它们的操作顺序(这是高度随机的),一个线程的值将覆盖另一个线程对主内存的写入。很难知道哪一个会赢得race condition

    请注意 - 原子性并不意味着“所有其他线程将被阻塞,直到值准备好。

    没错。这试图让您知道这里根本不涉及锁定。无法保证ThreadY 将看到的值。 ThreadY 也可以在同一时间将 i1 更新为值 3,然后其他线程可以将其视为 1、2 或 3,具体取决于操作顺序以及这些线程是否与 memory barriers 交叉时缓存刷新和更新被强制执行。

    我们控制线程之间共享的字段和对象的方式是使用synchronized 关键字,它赋予线程对资源的唯一访问权限。还有Locks 等机制提供互斥锁。我们还可以通过在字段中添加volatile 关键字来强制内存屏障,这意味着对该字段的任何读取或写入都将在主内存中进行。 synchronizedvolatile 都确保数据的正确发布和操作的顺序。

    【讨论】:

      猜你喜欢
      • 2017-08-12
      • 2016-05-20
      • 1970-01-01
      • 2015-07-16
      • 1970-01-01
      • 1970-01-01
      • 2015-08-13
      • 2016-04-04
      • 2013-10-14
      相关资源
      最近更新 更多