我一直在读一本名为《多处理器编程艺术》的书,遇到了诸如get()、getandset()、compareandset()、getandIncrease()、get andIncreace()等函数。
书中说,上面所有的函数都是原子函数,我同意,但我自己也怀疑一些函数是如何变成原子函数的。
为什么get或compare的函数变成原子函数?-因为它必须等待,直到它得到值,或者等待,直到某个条件变为真,从而产生一个屏障,因此是原子。
我这样想对吗?有什么东西我错过了吗?
当我做时
if (tail_index.get() == (head_index.getAndIncrement())
这是原子弹吗?
通过添加显式线程安全性,使方法相对于某个实例成为atomic
。在许多情况下,这是通过将方法标记为synchronized
来实现的。没有什么神奇的,如果你看看线程安全类的源代码,它声称方法是原子的,你会看到锁定。
WRT到你的第二部分,不,它不是原子的。每个方法调用都是原子调用,但当您将两个方法放在一起时,组合不是原子调用。CCD_ 3和CCD_。一旦您添加了其他代码(或调用的组合),除非您这样做,否则它就不是原子的。
如果函数看起来是瞬间发生的,那么它就是原子的[1]
在这里,"出现"是指从系统其他部分的角度来看。例如,考虑一个反转链表的同步函数。对于外部观察者来说,操作显然不是即时发生的:更新所有列表指针需要多次读取和写入。然而,由于锁定一直处于锁定状态,因此在此期间,系统的其他部分无法读取列表,因此对他们来说,更新看起来是即时的。
同样,CAS(compare-and-set)操作实际上不会在现代计算机上立即发生。一个CPU核心获得对该值的独占写入访问需要时间,然后另一个核心需要更多时间重新获得读取访问以查看新值。在此期间,CPU并行执行其他指令。为了确保即时执行的假象得以保留,JVM在CAS操作前后发出CPU指令,以确保在CAS完成之前不会提取和执行逻辑上后续的读取(例如,这将允许您在实际锁定之前读取链表的一部分),并且在CAS完成之后没有逻辑在先写入被延迟和执行(这将允许另一个线程在链表被完全更新之前取得锁)。
这些CPU排序指令是AtomicInteger.com.pareAndSet和AtomicIntel.weakCompareAndSet之间的关键区别("可能会错误失败"的位可以通过循环轻松纠正)。如果没有排序保证,弱CAS操作就无法用于实现大多数并发算法,并且"很少是compareAndSet的合适替代方案"。
如果这听起来很复杂。。。好是的!这就是为什么你仍然可以通过设计并发算法来获得博士学位。为了显示并发算法的正确性,您必须考虑其他线程可能在做什么来搅乱您。如果你把他们看作对手,试图打破原子性的幻想,这可能会有所帮助。例如,让我们考虑您的示例:
if (tail_index.get() == (head_index.getAndIncrement()))
我假设这是一种方法的一部分,该方法将一个项从堆栈中弹出,该堆栈被实现为具有索引计数器的循环数组,如果堆栈现在为空,则执行"if"的主体。由于head_index和tail_index是分别访问的,您的对手可以通过任意多的操作来"分割"它们。(例如,想象一下,您的线程在get和getAndIncrement之间被操作系统中断。)因此,他可以很容易地向堆栈中添加几十个项目,然后删除除一个项目外的所有项目,将head_index留在tail_index之上;即使您正在删除堆栈上的最后一项,if块也永远不会执行。
因此,当你的书说get()、getAndSet()等是原子的时,它并没有对这些方法的任何可能实现做出一般性的说明。它告诉您,Java标准保证它们是原子的,并通过仔细使用可用的CPU指令来做到这一点,这在普通Java中是不可能做到的(synchronized
允许您模拟它,但成本更高)。
get()
不是原子函数。但是,例如,getAndIncrement
或compareAndSet
本身就是原子。这意味着它保证了,所有的逻辑都是原子化的。对于get()
,还有另一个保证:当您将原子值发布到一个线程中时,它会立即对另一个线程可见(就像volatile字段一样)。非易失性和非原子值不:在某些情况下,设置为非易失字段的值对其他线程不可见;这些线程获取一个旧值读取字段的值。
但您总是可以使用Atomic*
类和其他同步原语来编写原子函数。