这个incrementAndGet是线程安全的吗?它似乎从缓存中提取对象



这个servlet似乎从ehCache中获取一个对象,从一个有这个对象的元素:http://code.google.com/p/adwhirl/source/browse/src/obj/HitObject.java?repo=servers-mobile

然后继续增加计数器,这是一个原子长的:

http://code.google.com/p/adwhirl/source/browse/src/servlet/MetricsServlet.java?repo=servers-mobile # 174

    //Atomically record the hit
    if(i_hitType == AdWhirlUtil.HITTYPE.IMPRESSION.ordinal()) {
        ho.impressions.incrementAndGet();
    }
    else {
        ho.clicks.incrementAndGet();
    }

这对我来说似乎不是线程安全的,因为多个线程可以从缓存中抓取,如果两者同时增加,你可能会失去点击/印象计数。

你同意这不是线程安全吗?

AtomicLongAtomicInteger在内部使用CAS——比较和设置(或比较和交换)。其思想是告诉CAS两件事:您希望long/int具有的值,以及您希望将其更新为的值。如果long/int具有您说它应该具有的值,CAS将自动进行更新并返回true;否则,它不会进行更新,它将返回false。许多现代芯片在机器码级别非常有效地支持CAS;如果JVM运行在没有CAS的环境中,它可以使用互斥体(Java称之为同步)来实现CAS。无论如何,一旦有了CAS,就可以通过以下逻辑(在伪代码中)安全地实现原子增量:

long incrementAndGet(atomicLong, byIncrement)
    do
        oldValue = atomicLong.get()            // 1
        newValue = oldValue + byIncrement
    while ! atomicLong.cas(oldValue, newValue) // 2
    return newValue

如果另一个线程进入并在// 1// 2行之间执行自己的增量操作,则CAS将失败,循环将再次尝试。

这种方法有一个风险:如果争用低,CAS比同步块快,则不太可能导致线程上下文切换。但是,如果存在很多争用,那么对于每个增量,有些线程将不得不进行多次循环迭代,这显然会浪费工作。一般来说,在大多数常见负载下,incrementAndGet会更快。

增量是线程安全的,因为AtomicInteger和family保证了这一点。但是从缓存中插入和抓取存在一个问题,其中可以创建和插入两个(或更多)HitObject。这可能会导致在第一次访问这个HitObject时丢失一些命中。@denis。solonenko指出,在代码中已经有一个TODO来修复这个问题。

然而,我想指出的是,这段代码只在第一次访问给定的HitObject时遭受并发。一旦在缓存中有了HitObject(并且没有更多的线程创建或插入HitObject),那么这段代码就是完全线程安全的。所以这只是一个非常有限的并发问题,可能这就是他们还没有修复它的原因。

最新更新