java.util.concurrent代码审查

时间:2011-02-06 12:31:54

标签: java concurrency memory-management

我正在研究java.util.concurrent库并在源代码中找到许多无限循环,就像这个

//java.util.concurrent.atomic.AtomicInteger by Doug Lea
public final int getAndSet(int newValue) {
    for (;;) {
        int current = get();
        if (compareAndSet(current, newValue))
            return current;
    }
}

我想知道,在什么情况下实际值不能等于预期值(在这种情况下compareAndSet返回false)?

4 个答案:

答案 0 :(得分:10)

许多现代CPU都compareAndSet()映射到原子硬件操作。这意味着,它是线程安全的而不需要同步(相比之下,这是一个相对昂贵的操作)。但是,它只有compareAndSet()本身就是原子,所以为getAndSet()(即将变量设置为给定值并返回它当时的值,而不可能将其设置为两者之间的不同值)代码使用技巧:首先获取值,然后使用刚刚获得的值和新值尝试compareAndSet()。如果失败,则变量由中间的另一个线程操纵,代码再次尝试。

如果compareAndSet()很少失败,即如果没有太多线程同时写入变量,这比使用同步更快。在许多线程始终写入变量的极端情况下,同步实际上可以更快,因为虽然存在同步开销,但是其他尝试访问变量的线程将等待并在轮到它时被唤醒,而不是不得不重复重试。

答案 1 :(得分:3)

当在另一个线程中修改该值时,get()和compareAndSet()可以看到不同的值。这是并发库需要担心的事情。

答案 2 :(得分:3)

这是无限循环,在处理TAS(测试和设置)算法时,这是一种很好的做法。循环的作用是(a)从内存中读取(应该是易失性语义)(b)计算新值(c)如果旧值未同时更改,则写入新值。< / p>

在数据库领域,这称为乐观锁定。它利用了大多数共享内存的并发更新是无争用的事实,在这种情况下,这是最便宜的方法。

事实上,这基本上是无偏见的Lock在无竞争情况下会做的事情。它将读取锁的值,如果它被解锁,它将执行线程ID的CAS,如果成功,则现在保持锁。如果失败,其他人首先获得锁定。锁定虽然以一种更复杂的方式处理失败案例,而不仅仅是一遍又一遍地重试op。他们会继续阅读它一段时间,因为锁被快速解锁(旋转锁定)然后通常进入睡眠状态以让其他线程进入直到轮到他们(指数退避)。

答案 3 :(得分:1)

以下是compareAndSet操作的实际用法:假设您设计了一个计算多线程内容的算法。

每个线程都会记住一个旧值,并根据它执行复杂的计算。

然后,如果旧值尚未被另一个计算线程更改,则它只想设置新结果。如果旧值不是预期值,则线程会丢弃其自己的工作,获取新值并重新开始计算。它使用compareAndSet

其他线程保证只获得新值以继续计算。

“无限”循环用于实现“忙等待”,这可能比将线程置于睡眠状态要便宜得多,尤其是当线程争用率较低时。

干杯!