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

时间:2011-12-19 22:29:59

标签: java multithreading concurrency ehcache

这个servlet似乎从具有该对象的Element中获取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();
    }

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

您是否同意这不是线程安全的?

2 个答案:

答案 0 :(得分:6)

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将成功。

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

答案 1 :(得分:0)

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

但是我想指出这个代码在首次访问给定的HitObject时只会遇到并发问题。一旦你在缓存中有HitObject(并且没有更多线程创建或插入HitObject),那么这段代码是完全线程安全的。所以这只是一个非常有限的并发问题,也许这就是他们尚未修复它的原因。