自旋锁(Spin lock)
自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。
简单的实现
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
private AtomicReference<Thread> owner = new AtomicReference<Thread>();
public void lock() {
Thread currentThread = Thread.currentThread();
// 如果锁未被占用,则设置当前线程为锁的拥有者
while (owner.compareAndSet(null, currentThread)) {
}
}
public void unlock() {
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,这样下一个线程看到这个变化,就退出自旋。
简单的实现
import java.util.concurrent.atomic.AtomicInteger;
public class TicketLock {
private AtomicInteger serviceNum = new AtomicInteger(); // 服务号
private AtomicInteger ticketNum = new AtomicInteger(); // 排队号
public int lock() {
// 首先原子性地获得一个排队号
int myTicketNum = ticketNum.getAndIncrement();
// 只要当前服务号不是自己的就不断轮询
while (serviceNum.get() != myTicketNum) {
}
return myTicketNum;
}
public void unlock(int myTicket) {
// 只有当前线程拥有者才能释放锁
int next = myTicket + 1;
serviceNum.compareAndSet(myTicket, next);
}
}
缺点
Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。
下面介绍的CLH锁和MCS锁都是为了解决这个问题的。
MCS 来自于其发明人名字的首字母: John Mellor-Crummey和Michael Scott。
CLH的发明人是:Craig,Landin and Hagersten。
CLH锁
CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class CLHLock {
public static class CLHNode {
private boolean isLocked = true; // 默认是在等待锁
}
@SuppressWarnings("unused" )
private volatile CLHNode tail ;
private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater
. newUpdater(CLHLock.class, CLHNode .class , "tail" );
public void lock(CLHNode currentThreadCLHNode) {
CLHNode preNode = UPDATER.getAndSet( this, currentThreadCLHNode); // 转载人注释: 把this里的"tail" 值设置成currentThreadCLHNode
if(preNode != null) {//已有线程占用了锁,进入自旋
while(preNode.isLocked ) {
}
}
}
public void unlock(CLHNode currentThreadCLHNode) {
// 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。
if (!UPDATER .compareAndSet(this, currentThreadCLHNode, null)) {
// 还有后续线程
currentThreadCLHNode. isLocked = false ;// 改变状态,让后续线程结束自旋
}
}
}
MCS锁
MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class MCSLock {
public static class MCSNode {
MCSNode next;
boolean isLocked = true; // 默认是在等待锁
}
volatile MCSNode queue ;// 指向最后一个申请锁的MCSNode
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater
. newUpdater(MCSLock.class, MCSNode. class, "queue" );
public void lock(MCSNode currentThreadMcsNode) {
MCSNode predecessor = UPDATER.getAndSet(this, currentThreadMcsNode);// step 1
if (predecessor != null) {
predecessor.next = currentThreadMcsNode;// step 2
while (currentThreadMcsNode.isLocked ) {// step 3
}
}
}
public void unlock(MCSNode currentThreadMcsNode) {
if ( UPDATER.get( this ) == currentThreadMcsNode) {// 锁拥有者进行释放锁才有意义
if (currentThread.next == null) {// 检查是否有人排在自己后面
if (UPDATER.compareAndSet(this, currentThreadMcsNode, null)) {// step 4
// compareAndSet返回true表示确实没有人排在自己后面
return;
} else {
// 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
// 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完
while (currentThreadMcsNode.next == null) { // step 5
}
}
}
currentThreadMcsNode.next.isLocked = false;
currentThreadMcsNode.next = null;// for GC
}
}
}
CLH锁 与 MCS锁 的比较
下图是CLH锁和MCS锁队列图示:
差异:
- 从代码实现来看,CLH比MCS要简单得多。
- 从自旋的条件来看,CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋
- 从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
- CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。
注意:这里实现的锁都是独占的,且不能重入的。
from:http://blog.csdn.net/fei33423/article/details/30316377
相关推荐
自旋锁不会引起调用者睡眠,如果一个执行线程试图获得一个已经被持有的自旋锁,那么线程就会一直进行忙循环,一直等待下去,在那里看是否该自旋锁的保持者已经释放了锁,\"自旋\"一词就是因此而得名。由于自旋锁使用...
linux 自旋锁讨论记录,自旋锁与信号量有哪些差别,这里给出答案
根据《多处理器编程的艺术》一书第七章“自旋锁与争用”编写的C++代码,演示了10种锁的实现。代码为本人学习研究所用,欢迎高手赐教。
信号量与自旋锁
本文章是关于信号量、互斥体和自旋锁的区别。
纯英文,专门解析自旋锁起因及解决方法,SQL SERVER DBA必备
linux系统中基于自旋锁的进程调度的实现, 有代码和详细的文档说明,自旋锁(spinlock) 是用C和汇编指令实现的,有助于了解linux系统 内核的加锁机制。 很不错的哦。。。
基于SMP的Linux内核自旋锁分析.pdf
一种Linux内核自旋锁死锁检测机制的设计与实现.pdf
轻量级锁 “轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是, 轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量 级锁使用产生的...
redis实现分布式锁,自旋式加锁,lua原子性解锁
线程同步(Thread Synchronization)是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads提供的锁机制(lock)来对多个线程之间共 享的临界区(Critical Section)进行保护(另一种常用的同步机制是barrier)。
在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,本文详细介绍了Java SE1.6中为了减少获得锁和释放...
对于互斥锁,如果资源已经被占用,自愿申请者只能进入睡眠状态。但自旋锁不会引起调用者睡眠
golang使用redis的setnx实现了一个自选锁,有key超时,同时也有我们调用redis链接时的超时。 package locker import ( context github.com/go-redis/redis runtime time ) type Lock struct { resource string...
Windows驱动编程视频教程 详尽的讲解 里面还有屏幕录制的录像
自旋锁操作 spin_lock 这里给出了一个示例程序和编译方法,大家可以在运行中体验自旋锁的操作。 后续还有些函数的使用说明。
在多核操作系统环境下,同一时刻多任务同时访问内核,自旋锁可以很好地处理不同处理器之间存在的同步与互斥问题,但自旋锁如果使用不当,极易产生死锁,造成应用层功能无法实现,所以很有必要对自旋锁展开重点测试。...
总结了 linux 设备驱动 自旋锁部分的思维导图
一、自旋锁 自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要...