php 自旋锁,[转]自旋锁、排队自旋锁、MCS锁、CLH锁

自旋锁(Spin lock)

自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。

简单的实现

importjava.util.concurrent.atomic.AtomicReference;publicclassSpinLock {privateAtomicReferenceowner=newAtomicReference();publicvoidlock() {

Thread currentThread=Thread.currentThread();//如果锁未被占用,则设置当前线程为锁的拥有者while(owner.compareAndSet(null, currentThread)) { }

}publicvoidunlock() {

Thread currentThread=Thread.currentThread();//只有锁的拥有者才能释放锁owner.compareAndSet(currentThread,null);

}

}

SimpleSpinLock里有一个owner属性持有锁当前拥有者的线程的引用,如果该引用为null,则表示锁未被占用,不为null则被占用。

这里用AtomicReference是为了使用它的原子性的compareAndSet方法(CAS操作),解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态

缺点CAS操作需要硬件的配合;

保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重;

没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁。

Ticket Lock

Ticket Lock 是为了解决上面的公平性问题,类似于现实中银行柜台的排队叫号:锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。

当线程释放锁时,将服务号加1,这样下一个线程看到这个变化,就退出自旋。

简单的实现

importjava.util.concurrent.atomic.AtomicInteger;publicclassTicketLock {privateAtomicInteger serviceNum=newAtomicInteger();//服务号privateAtomicInteger ticketNum=newAtomicInteger();//排队号publicintlock() {//首先原子性地获得一个排队号intmyTicketNum=ticketNum.getAndIncrement();//只要当前服务号不是自己的就不断轮询while(serviceNum.get()!=myTicketNum) { }returnmyTicketNum;

}publicvoidunlock(intmyTicket) {//只有当前线程拥有者才能释放锁intnext=myTicket+1;

serviceNum.compareAndSet(myTicket, next);

}

}

缺点

Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。

下面介绍的CLH锁和MCS锁都是为了解决这个问题的。

MCS 来自于其发明人名字的首字母: John Mellor-Crummey和Michael Scott。

CLH的发明人是:Craig,Landin and Hagersten。

MCS锁

MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。

importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;publicclassMCSLock {publicstaticclassMCSNode {volatileMCSNode next;volatilebooleanisBlock=true;//默认是在等待锁}volatileMCSNode queue;//指向最后一个申请锁的MCSNodeprivatestaticfinalAtomicReferenceFieldUpdater UPDATER=AtomicReferenceFieldUpdater

.newUpdater(MCSLock.class, MCSNode.class,"queue");publicvoidlock(MCSNode currentThread) {

MCSNode predecessor=UPDATER.getAndSet(this, currentThread);//step 1if(predecessor!=null) {

predecessor.next=currentThread;//step 2while(currentThread.isBlock) {//step 3}

}

}publicvoidunlock(MCSNode currentThread) {if(currentThread.isBlock) {//锁拥有者进行释放锁才有意义return;

}if(currentThread.next==null) {//检查是否有人排在自己后面if(UPDATER.compareAndSet(this, currentThread,null)) {//step 4//compareAndSet返回true表示确实没有人排在自己后面return;

}else{//突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者//这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完while(currentThread.next==null) {//step 5}

}

}

currentThread.next.isBlock=false;

currentThread.next=null;//for GC}

}

CLH锁

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;publicclassCLHLock {publicstaticclassCLHNode {privatebooleanisLocked=true;//默认是在等待锁}

@SuppressWarnings("unused")privatevolatileCLHNode tail ;privatestaticfinalAtomicReferenceFieldUpdaterUPDATER=AtomicReferenceFieldUpdater

. newUpdater(CLHLock.class, CLHNode .class,"tail");publicvoidlock(CLHNode currentThread) {

CLHNode preNode=UPDATER.getAndSet(this, currentThread);if(preNode!=null) {//已有线程占用了锁,进入自旋while(preNode.isLocked ) {

}

}

}publicvoidunlock(CLHNode currentThread) {//如果队列里只有当前线程,则释放对当前线程的引用(for GC)。if(!UPDATER .compareAndSet(this, currentThread,null)) {//还有后续线程currentThread. isLocked=false;//改变状态,让后续线程结束自旋}

}

}

CLH锁 与 MCS锁 的比较

下图是CLH锁和MCS锁队列图示:

4250648af32857396dcb49e7dbdbcefa.png

差异:从代码实现来看,CLH比MCS要简单得多。

从自旋的条件来看,CLH是在本地变量上自旋,MCS是自旋在其他对象的属性。

从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。

CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。

注意:这里实现的锁都是独占的,且不能重入的。

posted on 2015-08-07 00:18 DLevin 阅读(913) 评论(0)  编辑  收藏 所属分类: 收藏 、MultiThreading

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值