Below you will find pages that utilize the taxonomy term “并发 性能”
Posts
自旋锁的优化
自旋锁的提出主要也是为了解决多核系统的同步问题。当锁要保护的代码块执行很快的时候,并且争抢不是非常激烈的时候,自旋锁的比之重量级的需要切换上下文的互斥锁能带来更好的性能表现。对于非多核系统,用户态的自旋锁空耗 CPU,反而降低了整个系统的 progress,作用反而不大。
此外,当保护的代码块执行较为耗时,或者自旋锁的争抢非常激烈的时候,自旋锁本身就消耗了大量无谓的 CPU ,这种情况下还不如使用互斥锁,让出 CPU 给任务执行,提高实际的 CPU 利用率。
我在前面一篇介绍 CPU 高速缓存的博客,举了个例子用 AtomicInteger 的 compareAndSet 实现一个自旋锁,主要是为了演示在 SMP 下通过增加一个内循环,来减少锁状态 state 在多个 CPU 之间的『颠簸』。
但是其实这个例子如果用 synchronized 或者 ReentrantLock 改写,都会快得多。比如换成 ReentrantLock,
private ReentrantLock lock = new ReentrantLock(); /** * 递增 count,使用 ReentrantLock 保护 count */ public void incr() { lock.lock(); count = count + 1; lock.unlock(); } 运行时间下降到了 150~230 毫秒,比之原来测试的两个版本快了一个数量级。
原因就在于递增 +1 这个操作非常快,导致自旋锁版本的代码线程争抢锁非常激烈,大家抢的头破血流,但是任务却没有多大进展,空耗了大量 CPU。就好像一堆人抢着去上卫生间,大家都不排队,你挤我抢,反而堵在了门口,卫生间的实际利用率下降了。从这个生活中的例子出发,我们可以想到一些优化的方法,比如分散每个人的争抢时间,比如排队。
因此,自旋锁的优化有一些思路:
首先是退让(Back off),每个线程在争抢锁之前等待一小会,通常第一次不退让,但是之后就按照某个规则变化这个退让时间,我们修改下原来的 BusyLock 例子来演示这个优化,这里采用指数退让:
//最大退让时间 10 毫秒 private static final int max_backoff_msg=10; /** * 利用 CAS 实现自旋锁 */ private void lock() { //其实退让时间 1 毫秒。 int backoff=1; while (!