2011-02-06 101 views
4

我学习java.util.concurrent库,并在源代码中找到很多无限循环,像这样的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)?

+1

这是不是真的“无限”,因为其他线程保持你的*获取之后,对“无限”修改值的概率()*和你的* compareAndSet前右()*是如此之低,这是不是一个问题。在你被闪电击中的那天赢得全国彩票的可能性要高于你所写的无限循环。 – SyntaxT3rr0r 2011-02-06 12:54:17

+0

@ SyntaxT3rr0r,你可能不会参加国家彩票) – 2011-02-06 12:59:51

+0

@Stats,我强烈建议阅读一些理论。这里有一些初学者友好的文章:http://www.ibm.com/developerworks/java/library/j-jtp11234/ – bestsss 2011-02-06 13:33:38

回答

3

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

+0

+1。好。但为什么它不使用监视器来达到这个目的呢? – 2011-02-06 12:45:33

10

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

这比使用同步更快,如果compareAndSet()很少出现故障,即如果没有太多的线程同时写入变量。在很多线程总是向变量写入数据的极端情况下,同步实际上可能会更快,因为虽然存在同步开销,但尝试访问该变量的其他线程在轮到它时会等待并被唤醒,而不是必须重复操作。

1

下面是compareAndSet操作的实际用法:想象一下,您设计的算法可以计算多个线程中的某些内容。

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

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

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

“无限”循环用于实现“繁忙等待”,这可能比让线程休眠特别是在线程争用率低时更便宜。

干杯!

3

这是而不是一个无限循环,这是处理TAS(测试和设置)算法时的良好实践。 (a)从内存中读取(应该是易失性语义)(b)计算一个新值(c)如果旧值没有同时改变,则写入新值

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

事实上,这基本上是一个无偏见的锁将在无条件的情况下做。它将读取锁的值,如果它被解锁,它将执行线程ID的CAS并且如果成功,则锁现在被保持。如果失败,其他人首先获得锁定。尽管用一种更加复杂的方式来处理失败案例,而不是一遍又一遍地重试操作。他们会继续阅读它一会儿,然后锁快速解锁(旋转锁定),然后通常休息一下,让其他线程进入,直到他们轮到(指数退避)。