编程中的“原子”是什么意思?


273

在有效的Java书中,它指出:

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

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


24
一次执行一次操作。
Subhrajyoti Majumder

1
一次只能对该变量执行一次操作。
kaysush


1
我怀疑哲学问题属于codereview.stackexchange.com
2014年

请注意,某些变量默认情况下不具有原子读写功能,因此将它们声明为volatile longvolatile double使其成为原子读写操作。
H2ONaCl

Answers:


372

这是一个示例,因为一个示例通常比冗长的解释更清晰。假设foo是类型为的变量long。以下操作不是原子操作:

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;

75
因此,这是假定它在32位系统中运行。如果是64位系统怎么办?foo = 65465498L; 那么是原子的?
哈克2013年

46
@Harke如果您正在运行64位Java,则可以。
Jeroen 2014年

4
这也适用于C#和.NET吗?如果是,为了foo获得原子行为,CLR必须是64位?
Fabiano

5
@Fabiano它确实适用,这是在.NET中实现它的方法,因为我们没有像Java这样的synced关键字。stackoverflow.com/questions/541194/...
松饼人

2
然后,假设线程A分配了long值,然后线程B尝试以一半的方式读取它。如果操作A是原子操作,那么线程B将等待直到完成?这意味着原子操作将提供隐式线程安全性吗?
Teoman shipahi

60

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


25

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

原子性是与并发进程隔离的保证。此外,原子操作通常具有成功或失败的定义-它们要么成功更改系统状态,要么没有明显的作用。

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

您的报价突出显示了这并非在所有情况下都是预期的行为。


15

刚刚发现“ 原子与非原子操作”一文对我很有帮助。

“在共享内存上执行的操作相对于其他线程仅一步即可完成。

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

当对共享变量执行原子加载时,它将读取单个时间点出现的整个值。”


14

如果您有多个线程在下面的代码中执行方法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在两者之间观察到) 。


您为什么认为“ long”会导致问题,而“ int”不会呢?请在这里看到geekswithblogs.net/BlackRabbitCoder/archive/2012/08/09/...
onmyway133

1
@entropy long和double分配在Java中不保证是原子的。因此,您可以读很长的内容,其中在分配后仅更新了一半的位。
assylias 2013年

0

在Java中,除了long和double以外,所有类型的读取和写入字段都是原子发生的,并且如果使用volatile修饰符声明了该字段,则即使long和double也会被原子读取和写入。也就是说,我们得到的结果是100%,或者那里发生了什么,变量中也没有中间结果。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.