321

在 Effective Java 书中,它指出:

语言规范保证读取或写入变量是原子的,除非变量是类型longdouble[JLS, 17.4.7]。

在 Java 编程或一般编程的上下文中,“原子”是什么意思?

4

6 回答 6

422

下面是一个例子:假设foo是一个 type 的变量long,那么下面的操作不是原子操作(在 Java 中):

foo = 65465498L;

实际上,变量是使用两个单独的操作写入的:一个写入前 32 位,第二个写入最后 32 位。这意味着另一个线程可能会读取 的值foo,并查看中间状态。

使操作原子化包括使用同步机制,以确保从任何其他线程将操作视为单个原子(即不可拆分为部分)操作。这意味着任何其他线程,一旦操作成为原子操作,将foo在分配之前或之后看到 的值。但绝不是中间值。

一个简单的方法是使变量 volatile

private volatile long foo;

或者同步对变量的每次访问:

public synchronized void setFoo(long value) {
    this.foo = value;
}

public synchronized long getFoo() {
    return this.foo;
}
// no other use of foo outside of these two methods, unless also synchronized

或将其替换为AtomicLong

private AtomicLong foo;
于 2013-02-24T17:07:55.717 回答
79

“原子操作”是指从所有其他线程的角度来看似乎是瞬时的操作。当保证适用时,您无需担心部分完成的操作。

于 2014-06-25T13:25:19.903 回答
30

它是“在系统的其余部分看来是瞬间发生的”,属于计算过程中的线性化分类。进一步引用该链接文章:

原子性是与并发进程隔离的保证。此外,原子操作通常有一个成功或失败的定义——它们要么成功地改变了系统的状态,要么没有明显的效果。

因此,例如,在数据库系统的上下文中,可以有“原子提交”,这意味着您可以将更新的变更集推送到关系数据库,这些变更要么全部提交,要么根本不提交发生故障时,数据不会损坏,并且锁和/或队列的后果,下一个操作将是不同的写入或读取,但仅事实发生之后。在变量和线程的上下文中,这几乎是相同的,适用于内存。

您的引用强调,这不一定是所有情况下的预期行为。

于 2013-02-24T17:00:30.057 回答
18

刚刚发现一篇文章Atomic vs. Non-Atomic Operations对我很有帮助。

“作用于共享内存的操作是原子的,如果它相对于其他线程在一个步骤中完成。

当在共享内存上执行原子存储时,没有其他线程可以观察到修改半完成。

当对共享变量执行原子加载时,它会读取在某个时刻出现的整个值。”

于 2014-09-29T05:40:51.827 回答
15

如果您有多个线程执行以下代码中的方法 m1 和 m2:

class SomeClass {
    private int i = 0;

    public void m1() { i = 5; }
    public int m2() { return i; }
}

您可以保证任何线程调用m2都将读取 0 或 5。

另一方面,使用此代码(i长在哪里):

class SomeClass {
    private long i = 0;

    public void m1() { i = 1234567890L; }
    public long m2() { return i; }
}

线程调用m2可以读取 0、1234567890L 或其他一些随机值,因为i = 1234567890L不能保证语句对于 a 是原子的long(JVM 可以在两个操作中写入前 32 位和后 32 位,并且线程可能会i在两者之间观察) .

于 2013-02-24T17:09:45.903 回答
1

在 Java 中,除了 long 和 double 之外的所有类型的字段都是原子读写的,如果使用 volatile 修饰符声明该字段,即使 long 和 double 也是原子读写的。也就是说,我们得到 100% 的结果,或者那里发生了什么,变量中也不可能有任何中间结果。

于 2020-04-13T07:14:43.733 回答