您的位置:首页 > 其它

CAS自旋锁

2016-01-05 15:03 225 查看
昨天写了篇关于AtomicInteger的博客,感觉觉还是不太完整,所以又把自旋锁的知识整理了一下。。。。。。。。。

什么是自旋锁

       说道自旋锁就要从多线程下的锁机制说起,由于在多处理器系统环境中有些资源因为其有限性,有时需要互斥访问(mutual exclusion),这时会引入锁的机制,只有获取了锁的进程才能获取资源访问。即每次只能有且只有一个进程能获取锁,才能进入自己的临界区,同一时间不能两个或两个以上进程进入临界区,当退出临界区时释放锁。

     设计互斥算法时总是会面临一种情况,即没有获得锁的进程怎么办?

     通常有2种处理方式:

           一种是没有获得锁的调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,这就是本文的重点——自旋锁。他不用将线城阻塞起来(NON-BLOCKING)。

          另一种是没有获得锁的进程就阻塞(BLOCKING)自己,继续执行线程上的其他任务,这就是 ——互斥锁(包括内置锁Synchronized还有ReentrantLock等等)。

自旋锁是如何实现的呢???

     跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式(有时我也感觉自旋锁严格意义上称不上是一种锁)。

    通过了解自旋锁的原理其实不难发现,这种锁是有缺陷的:

    归死锁:试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。

     所以在递归程序中使用自旋锁应遵守下列策略:

           递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。

           此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。

     过多占用cpu资源:如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了.
因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会。

综上所述:自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

Java中的CAS

      CAS是一种系统原语(所谓原语属于操作系统用语范畴。原语由若干条指令组成的,用于完成一定功能的一个过程。primitive or atomic action 是由若干个机器指令构成的完成某种特定功能的一段程序,具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断)。CAS是Compare And Set的缩写。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

      sun.misc.Unsafe是JDK里面的一个内部类(哈哈。。。有没有发现AtomicInteger源码中中就有一个Unsafe的成员变量啊),这个类为JDK严格保护,因为他提供了大量的低级的内存操作和系统功能。如果因为错误的使用了这个类,不会有“异常”被扔出,甚至会造成JVM宕机。这也是为什么这个类的名字是Unsafe的原因。因此当使用这个类的时候,你一定要明白你在干什么(不过一般谁会用它呀)。这个类中提供了3个CAS的操作:

方法名解释
compareAndSwapInt(Object object, long address, int expected, int newValue) 比较对象object的某个int型的属性(以地址的方式访问),如果他的数据值是expected,则设定为newValue,返回true;否则返回false
compareAndSwapLong(Object object, long address, long expected, long newValue) 比较对象object的某个long型的属性(以地址的方式访问),如果他的数据值是expected,则设定为newValue,返回true;否则返回false
compareAndSwapLong(Object object, long address, Object expected, Object newValue) 比较对象object的某个Object型的属性(以地址的方式访问),如果他的数据值是expected,则设定为newValue,返回true;否则返回false
看到没有,AtomicInteger中的compareAndSet()方法就是表中红色字体的方法实现的。下面看看AtomicInteger的源码就一目了然了:

import sun.misc.Unsafe;

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;

// setup to use Unsafe.compareAndSwapInt for updates
<span style="color:#ff0000;">private static final Unsafe unsafe = Unsafe.getUnsafe(); </span>
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

//如果当前值 ==预期值,则以原子方式将该值设置为给定的更新值。如果成功就返回,否则返回,并且不修改原值。
public final boolean compareAndSet(int expect, int update) {
return <span style="color:#ff0000;">unsafe.compareAndSwapInt(this, valueOffset, expect, update);  </span>
}

//以原子方式将当前值加1。相当于线程安全版本的i++操作。
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}

}


从源代码中可以看出,Atomic包中的原子类就是基于自旋锁实现的!!!!!!!!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: