Java 函数是如何变成原子的?

Java 函数是如何变成原子的?,java,concurrency,parallel-processing,atomic,lock-free,Java,Concurrency,Parallel Processing,Atomic,Lock Free,我一直在读一本名为《多处理器编程的艺术》(art of multiprocessor programming)的书,遇到了诸如get()、getandset()、compareandset()、getandIncrease()、getandIncrease()等函数 书中说,上面所有的函数都是原子函数,我同意,但我自己对某些函数如何变成原子函数有疑问 为什么带有get或compare的函数变成原子函数因为它必须等待,直到它得到值,或者等待,直到某个条件变为真,这会产生一个障碍,因此原子 我这样想

我一直在读一本名为《多处理器编程的艺术》(art of multiprocessor programming)的书,遇到了诸如get()、getandset()、compareandset()、getandIncrease()、getandIncrease()等函数

书中说,上面所有的函数都是原子函数,我同意,但我自己对某些函数如何变成原子函数有疑问

为什么带有getcompare的函数变成原子函数因为它必须等待,直到它得到值,或者等待,直到某个条件变为真,这会产生一个障碍,因此原子

我这样想对吗?有什么我错过的吗

当我这样做的时候

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

这是原子的吗?

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


请注意你的第二部分,不,它不是原子的。每个方法调用都是原子性的,但当您将两个方法组合在一起时,组合就不是原子性的
get
getAndIncrement
已显式设置为原子。一旦您添加了其他代码(或调用的组合),它就不是原子的,除非您这样做。

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


请注意你的第二部分,不,它不是原子的。每个方法调用都是原子性的,但当您将两个方法组合在一起时,组合就不是原子性的
get
getAndIncrement
已显式设置为原子。一旦您添加了其他代码(或调用的组合),它就不是原子的,除非您这样做。

否,使用
get()
的函数不是原子的。但是,例如,
getAndIncrement
compareAndSet
本身就是原子的。这意味着它保证了,所有的逻辑都是原子化的。对于
get()。非易失性和非原子值:在某些情况下,设置为非易失性域的值对其他线程不可见;这些线程获取旧值读取字段的值


但您始终可以使用
原子*
类和其他同步原语编写原子函数。

不,使用
get()
的函数不是原子函数。但是,例如,
getAndIncrement
compareAndSet
本身就是原子的。这意味着它保证了,所有的逻辑都是原子化的。对于
get()。非易失性和非原子值:在某些情况下,设置为非易失性域的值对其他线程不可见;这些线程获取旧值读取字段的值


但您始终可以使用
atomic*
类和其他同步原语编写原子函数。

如果函数看起来是瞬时发生的,那么它就是原子的。[]

这里,“似乎”是指从系统其他部分的角度来看。例如,考虑一个反转链表的同步函数。对于外部观察者来说,该操作显然不是瞬时发生的:它需要多次读写来更新所有列表指针。但是,由于锁一直处于锁定状态,因此在这段时间内,系统的任何其他部分都无法读取列表,因此对他们来说,更新似乎是即时的

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

这些CPU排序指令是和之间的关键区别(“可能会错误地失败”位很容易通过循环得到纠正)。如果没有排序保证,弱CAS操作无法用于实现大多数并发算法,并且“很少是compareAndSet的合适替代方案”

如果这听起来很复杂…嗯…是的!这就是为什么。为了显示一个并发算法的正确性,你必须考虑其他线程可能会做些什么来搅乱你。如果你认为他们是对手,试图打破原子性的幻觉,这可能会有所帮助。例如,让我们考虑一下你的例子:

if (tail_index.get() == (head_index.getAndIncrement()))
我假设这是一个方法的一部分,该方法将从堆栈中弹出一个项,该堆栈实现为带有索引计数器的循环数组,如果堆栈现在为空,则执行“if”的主体。由于head_索引和tail_索引是分开访问的,因此您的对手可以使用任意数量的操作来“分割”它们。(例如,假设您的线程被get和getAndIncrement之间的操作系统中断。)因此,他可以很容易地将几十个项目添加到