函数如何成为原子?

时间:2013-11-25 17:02:52

标签: java concurrency parallel-processing atomic lock-free

我一直在阅读这本名为“多处理器编程艺术”的书,并遇到了诸如get(),getandset(),compareandset(),getandIncrease(),getandIncrease()等函数。

在书中它说上述所有功能都是原子的,我同意,但我对一些功能如何成为原子功能有我自己的怀疑。

为什么 获取 比较 的功能会变为原子? - 因为它必须等到它获得值或等待直到某些条件变为真,这会产生一个障碍,因此是原子的。

我这样思考是对的吗?有没有我错过的东西?

当我这样做时

 if (tail_index.get() == (head_index.getAndIncrement()) 

这是原子吗?

3 个答案:

答案 0 :(得分:4)

通过添加显式线程安全性,使方法相对于某个实例成为atomic。在许多情况下,这是通过将方法标记为synchronized来完成的。没有魔法,如果你看一下声称方法是原子的线程安全类的源代码,你会看到锁定。

WRT到你的第二部分,不,它不是原子的。每个方法调用都是原子的,但当你把两个放在一起时,这个组合不是原子的。 getgetAndIncrement已经明确地成为原子。一旦你添加了其他代码(或者调用的组合),除非你这样做,否则它不是原子的。

答案 1 :(得分:1)

不,函数,使用get()不是原子的。但是,例如,getAndIncrementcompareAndSet本身就是原子的。这意味着它保证了所有逻辑都是原子化的。对于get(),还有另一个保证:当您将原子值发布到一个线程中时,它会立即变为另一个线程可见(就像volatile字段一样)。非易失性和非原子值不要:有些情况,其中设置为非易失性fiels的值对另一个线程不可见;这些线程获得旧值读取字段的值。

但是你总是可以使用Atomic*类和其他同步原语来编写原子函数。

答案 2 :(得分:1)

如果函数似乎是瞬间发生的,则该函数为原子 [1]

这里,“似乎”意味着从系统其余部分的角度来看。例如,考虑一个反转链表的同步函数。对于外部观察者来说,操作显然不会立即发生:需要多次读取和写入才能更新所有列表指针。但是,由于锁定始终处于保持状态,因此系统的任何其他部分都不会在此期间读取列表,因此对于他们来说,更新会立即显示。

同样,CAS(比较和设置)操作实际上不会在现代计算机上立即发生。一个CPU内核需要一段时间才能获得对该值的独占写访问权,然后另一个内核需要更多时间才能重新获取读访问权以查看新值。在此期间,CPU正在并行执行其他指令。为了确保保留瞬时执行的错觉,JVM在CAS操作之前和之后发出CPU指令,以确保在CAS完成之前没有逻辑后续读取被拉出并执行(这将允许您在之前读取链接列表的一部分)你实际上已经获得了锁定,并且在CAS完成后没有逻辑上先写入的内容被延迟并执行(这将允许另一个线程在链接列表完全更新之前获取锁定)。

这些CPU排序指令是AtomicInteger.compareAndSetAtomicInteger.weakCompareAndSet之间的关键区别(“可能失败的虚假”位很容易通过循环纠正)。如果没有排序保证,弱CAS操作不能用于实现大多数并发算法,并且“很少是compareAndSet的合适替代”。

如果这听起来很复杂......那就好了!这就是you can still get a PhD by designing a concurrent algorithm的原因。要显示并发算法的正确性,您必须考虑每个其他线程可能正在做什么来搞乱您。如果你认为它们是对手,试图打破原子性的错觉,它可能会有所帮助。例如,让我们考虑一下你的例子:

if (tail_index.get() == (head_index.getAndIncrement()))

我认为这是将一个项目从作为带有索引计数器的循环数组实现的堆栈弹出的方法的一部分,如果堆栈现在为空,则执行“if”的主体。由于head_index和tail_index是分开访问的,你的对手可以根据自己喜欢的操作“划分”它们。 (想象一下,例如,你的线程被get和getAndIncrement之间的操作系统中断了。)所以他很容易将几十个项目添加到堆栈中,然后删除除了一个之外的所有项目,将head_index 保留在上面 tail_index;即使您要删除堆栈中的最后一项,if块也将永远不会执行。

因此,当你的书中说get(),getAndSet()等是原子的时,它并没有对这些方法的任何可能实现做出一般性陈述。它告诉你Java标准保证它们是原子的,并且通过谨慎使用可用的CPU指令来实现这一点,这种方式在普通Java中是不可能的( {{ 1}} 让你模仿它,但成本更高。)