非阻塞方法中的饥饿

时间:2012-01-28 08:48:40

标签: java multithreading performance nonblocking

我一直在阅读非阻塞方法。这是所谓的无锁计数器的一段代码。

public class CasCounter {
private SimulatedCAS value;

public int getValue() {
    return value.get();
}

public int increment() {
    int v;
    do {
        v = value.get();
    }
    while (v != value.compareAndSwap(v, v + 1));
    return v + 1;
}

}

我只是想知道这个循环:

do {
    v = value.get();
}
while (v != value.compareAndSwap(v, v + 1));

人们说:

所以它再次尝试,直到尝试更改值的所有其他线程都这样做。这是免锁的,因为没有锁使用,但没有阻塞,因为它可能不得不再次尝试(这是罕见的)不止一次(非常罕见)。

我的问题是:

他们怎么能这么肯定?至于我,我看不出为什么这个循环不能无限的任何理由,除非JVM有一些特殊的机制来解决这个问题。

2 个答案:

答案 0 :(得分:2)

编辑:我想我现在有一个满意的答案。困扰我的是“v!= compareAndSwap”。在实际代码中,如果值等于比较表达式,CAS将返回true。因此,即使第一个线程在get和CAS之间中断,第二个线程也将继续交换并退出方法,因此第一个线程将能够执行CAS。

当然,如果两个线程无限次地调用此方法,则有可能其中一个根本没有机会运行CAS,特别是如果它具有较低的优先级,但这是其中之一不公平锁定的风险(但概率非常低)。正如我所说,队列机制能够解决这个问题。

对于最初错误的假设感到抱歉。

答案 1 :(得分:2)

循环可以是无限的(因为它可以为你的线程生成饥饿),但发生这种情况的可能性非常小。为了让你得到饥饿,你需要一些其他的线程成功地改变你想要在你的阅读和你的商店之间更新的价值,并为此重复发生。

有可能编写代码来触发饥饿,但对于真正的程序来说,它不太可能发生。

当您认为不会经常发生写入冲突时,通常会使用比较和交换。比如当你更新时有50%的机会“错过”,那么有25%的几率你会错过两个循环,并且在10个循环中没有更新成功的可能性不到0.1%。对于现实世界的例子,50%的未命中率非常高(基本上没有做任何事情而不是更新),并且随着未命中率降低,比如1%,那么在两次尝试中不成功的风险仅为0.01%且在3中尝试0.0001%。

用法类似于以下问题

将变量a设置为0并且有两个线程同时更新它a = a + 1和100万次。最后一个可以在1000000之间得到任何答案(每次其他更新因覆盖而丢失)和2000000(没有更新被覆盖)。

越接近2000000,您越有可能使用CAS,因为这意味着CAS通常会看到预期值并能够使用新值进行设置。