原子操作的工作原理以及如何不能抢占线程。是操作系统保证还是 JVM 保证?



我试图理解原子操作是如何工作的,特别是在Java中。

AtomicInteger.该文件说它是:"一个可以通过原子方式更新的 int 值。例如,此类的原子操作之一是:

/**
* Atomically sets to the given value and returns the old value.
*
* @param newValue the new value
* @return the previous value
*/
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}

根据文档,可以保证这将是原子操作。但是,实际方法unsafe.getAndSetInt()至少要执行几行。那么如何保证原子性呢?

例如,如果线程 A 当前正在执行此代码,为什么不能抢占?据我了解,它是操作系统的调度程序,它将在其他线程之间共享时间片,如果线程正在执行某种原子方法,则如何决定,然后需要执行所有指令。

这种安排是在操作系统级别完成的吗?JVM、API 调用和操作系统之间是否存在契约,如果一个线程执行 someFoo(( 方法(假设是原子的(,那么它是原子的,并且需要由该线程完成而不会被抢占?

在某些体系结构上,有一些指令保证以原子方式读取旧值并写入新值。 其他人使用一对称为"加载链接/条件存储"(ll/cs(的操作。 加载链接操作将加载一个值并配置硬件以监视是否发生了任何可能干扰它的事情。 仅当自上次加载链接以来没有发生任何可能干扰写入值的事件时,条件存储才会写入值,并且 if 将指示(例如,通过将寄存器设置为 0 或 1(存储是否成功。 例如,如果条件存储成功时返回 1,则交换操作可以实现为:

int exchange(int *ptr, int newValue)
{
int oldValue;
do
{
oldValue = loadLinked(ptr);
} while (!conditionalStore(ptr, newValue);
return oldValue;
}

如果有任何东西干扰了链接加载和条件存储之间的指示地址,则条件存储除了返回零之外不会产生任何副作用。 如果发生这种情况,循环将重复链接加载并存储条件,直到两个操作设法连续发生。

请注意,许多体系结构保证,如果某个位置受到干扰,存储条件将始终报告失败,但它们通常不承诺在位置未受干扰时始终报告成功。 根据体系结构的不同,可能会有很多或很少的误报,并且像交换这样的操作的效率可能会受到数量的影响。 例如,每当另一个线程将任何内容写入与正在监视的对象相同的缓存行时,实现都可能导致条件存储失败,即使该线程在该缓存行中写入不同的对象也是如此。 这可能会降低性能,但如果唯一可能导致条件存储失败的是另一个线程上的成功存储,则程序仍然可以共同取得进展。 如果两个线程各自执行一个加载链接,然后每个线程执行条件存储的效果是两个存储都将失败,则线程可能会活锁。 这可以通过添加随机延迟来缓解,但在大多数使用场景中,这不是必需的。

最新更新