如原子的软件包文档中所述(通常,不是特定的更新程序):
原子的访问和更新的记忆效应通常遵循易失性的规则,[...]:
-
get
具有读取变量的记忆效果。volatile
-
set
具有写入(分配)变量的记忆效果。volatile
- [...]
-
compareAndSet
以及所有其他读取和更新操作,例如具有读取和写入变量的内存效果。getAndIncrement
volatile
原子试图解决什么问题?为什么使用(例如)而不是 ?它不是试图解决并发读取的任何问题,因为这些已经由常规.(“易失性”读取或写入与“原子”读取或写入相同。仅当并发读取发生在写入过程中,或者如果语句以某种有问题的方式重新排序或优化时,它才会成为问题;但已经阻止了这些事情。唯一解决的问题是,在这种方法中,当我们读取()和写入它()之间,其他一些线程可能会并发写入。 通过在此期间锁定任何竞争写入来解决此问题。compareAndSet
atomicInteger.compareAndSet(1,2)
if(volatileInt == 1) { volatileInt = 2; }
volatile
volatile
compareAndSet
volatileInt
volatileInt
volatileInt == 1
volatileInt = 2
compareAndSet
在“更新程序”(等)的特定情况下也是如此:读取仍然只是桃色的。更新程序方法的唯一限制是,它们不是像我上面写的那样“锁定任何竞争写入”,而是只锁定来自同一个 AtomicReferenceFieldUpdater
实例的竞争写入;当您同时直接更新字段时(或者,就此而言,当您同时使用多个来更新同一字段时),它们无法保护您。(顺便说一句,取决于你如何看待它 - 同样的事情也是如此:如果你以绕过他们自己的设置者的方式更新他们的领域,他们就无法保护你。不同之处在于,一个实际上拥有它的字段,而且它是 ,所以没有必要警告你不要以某种方式通过外部手段修改它。AtomicReferenceFieldUpdater
volatile
compareAndSet
volatile
AtomicReferenceFieldUpdater
volatile
AtomicReference
AtomicReference
private
因此,为了回答您的问题:是的,您可以继续读取具有相同原子性的字段,以防止部分/不一致的读取,防止语句被重新排序等。volatile
编辑添加(12月6日):任何对这个主题特别感兴趣的人都可能对下面的讨论感兴趣。我被要求更新答案,以澄清该讨论的要点:
我认为最重要的一点是,以上是我自己对文档的解释。我相当有信心我已经正确地理解了它,并且没有其他解释是有意义的;如果需要,我可以详细论证这一点;-) ;但是我和其他人都没有对任何权威文档的引用,这些文档比问题本身中提到的两个文档(该类的Javadoc和Java Concurrency in Practice)以及我上面对它的原始答案中提到的一个文档(包的Javadoc)更明确地解决了这一点。
-
我认为,下一个最重要的一点是,尽管 的文档说与易失性写入混合是不安全的,但我相信在典型的平台上,它实际上是安全的。只有在一般情况下才不安全。我之所以这么说,是因为包文档中的以下注释:AtomicReferenceUpdater
compareAndSet
这些方法的规范使实现能够采用当代处理器上可用的高效机器级原子指令。但是,在某些平台上,支持可能需要某种形式的内部锁定。因此,不能严格保证这些方法不是阻塞的 - 线程可能会在执行操作之前暂时阻塞。
所以:
- 在现代处理器的典型 JDK 实现中,只需使用易失性写入,因为使用相对于易失性写入的原子比较和交换操作。 必然比 更复杂,因为它必须使用类似反射的逻辑来更新另一个对象中的字段,但我坚持认为这是它更复杂的唯一原因。一个典型的实现调用 ,这是一个按较长名称的易失性写入。
AtomicReference.set
AtomicReference.compareAndSet
AtomicReferenceUpdater.set
AtomicReference.set
Unsafe.putObjectVolatile
- 但并非所有平台都支持这种方法,如果不支持,则允许阻止。冒着过度简化的风险,我认为这大致意味着原子类可以通过(或多或少地)应用于使用和直接使用的方法来实现。但是要使这起作用,也必须是,原因在我上面的原始答案中解释;也就是说,它不能只是一个易失性的写入,因为这样它可以在已调用之后但在调用之前修改字段。
compareAndSet
synchronized
get
set
set
synchronized
compareAndSet
get
compareAndSet
set
- 毋庸置疑,我最初的答案使用“锁定”一词不应该从字面上理解,因为在典型的平台上,没有什么非常像锁定的需要。
-
在 Sun 的 JDK 1.6.0_05 实现中,我们发现:java.util.concurrent.ConcurrentLinkedQueue<E>
private static class Node<E> {
private volatile E item;
private volatile Node<E> next;
private static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater =
AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
private static final AtomicReferenceFieldUpdater<Node, Object> itemUpdater =
AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "item");
Node(E x) { item = x; }
Node(E x, Node<E> n) { item = x; next = n; }
E getItem() { return item; }
boolean casItem(E cmp, E val)
{ return itemUpdater.compareAndSet(this, cmp, val); }
void setItem(E val) { itemUpdater.set(this, val); }
Node<E> getNext() { return next; }
boolean casNext(Node<E> cmp, Node<E> val)
{ return nextUpdater.compareAndSet(this, cmp, val); }
void setNext(Node<E> val) { nextUpdater.set(this, val); }
}
(注意:为紧凑性调整了空格),其中,一旦构建了实例,就没有易失性写入 - 也就是说,所有写入都是通过 or - 但易失性读取似乎是自由使用的,没有一次调用。后来的 JDK 1.6 版本被更改为直接使用(Oracle 的 JDK 1.6.0_27 已经发生了这种情况),但是 JSR 166 邮件列表上的讨论将这一变化归因于性能考虑,而不是对先前实现的正确性的任何疑虑。AtomicReferenceFieldUpdater.compareAndSet
AtomicReferenceFieldUpdater.set
AtomicReferenceFieldUpdater.get
Unsafe
- 但我必须指出,这不是防弹权威。为了方便起见,我写了“Sun的实现”,就好像它是一个统一的东西一样,但我之前的要点清楚地表明,针对不同平台的JDK实现可能必须以不同的方式做事。在我看来,上面的代码似乎是以平台中立的方式编写的,因为它避开了普通的易失性写入,转而支持对 ;但是不接受我对一点的解释的人可能不接受我对另一点的解释,并且可能会争辩说上述代码并不意味着对所有平台都是安全的。
AtomicReferenceFieldUpdater.set
- 这个权限的另一个弱点是,虽然似乎允许不稳定读取与调用同时进行,但它是一个私有类;而且我没有采取任何证据证明其所有者()实际上在没有自己的预防措施的情况下拨打此类电话。(但是,尽管我还没有证明这一说法,但我怀疑是否有人会对此提出异议。
Node
AtomicReferenceFieldUpdater.compareAndSet
ConcurrentLinkedQueue
有关本附录的背景和进一步讨论,请参阅以下评论。