自旋队列锁
来源:互联网 发布:nginx保持会话 编辑:程序博客网 时间:2024/06/03 20:49
1、自旋锁简介
自旋锁是为保护共享资源而提出一种锁机制。自旋锁与互斥锁比较类似,都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。
自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。另外格外注意一点:自旋锁不能递归使用。
cache一致性流量是指所有线程都在同一个共享存储单元上旋转,当执行一些原子操作的时候会导致其他线程该变量的cache副本失效,所以其他线程发生一次cache缺失,重新读值所引起的总线流量。
2、ArrayLock
public class ArrayLock implements Lock { private AtomicBoolean[] flag; private int size; private AtomicInteger tail = new AtomicInteger(0); ThreadLocal<Integer> mIdx = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public ArrayLock(int size) { if (size <= 0) { throw new IllegalArgumentException("size <= 0"); } flag = new AtomicBoolean[size]; for (int i = 0; i < size; i++) { flag[i] = new AtomicBoolean(false); } this.size = size; flag[0].set(true); } @Override public void lock() { int idx = tail.getAndIncrement() % size; mIdx.set(idx); while (!flag[idx].compareAndSet(true, false)) { } } @Override public void unlock() { int idx = mIdx.get(); flag[(idx + 1)%size].set(true); mIdx.set(0); }}
一个简单的环形数组锁,当ArrayLock初始化的时候,会初始化一个size大小的AtomicBoolean数组,为了防止过多的线程在一个存储单元上自旋,导致增加cache一致性流量。当lock的时候会给当前节点分配一个下标,在下标%size对应位置的AtomicBoolean上自旋,如果有上一个节点释放锁后,cas操作成功则可以进入临界区。unlock的时候,会将flag中下个AtomicBoolean设置为true,通知下个节点可以进入临界区了。多个节点可能对应flag中的一个slot,在这个slot被设置为true的时候,多个节点开始抢占,抢占成功的就可以进入临界区。如果多个一个slot上对应多个节点还是会增加cache一致性流量,所以还是要预测size的大小,保证每个slot上对应的节点数尽可能的小,而又不浪费flag中的空间。
3、CLHLoc
public class CLHLock implements Lock { public static final int FREE = 1; public static final int WAITING = 1 << 1; public static final int RELEASED = 1 << 2; public AtomicReference<Node> tail = new AtomicReference<Node>(); ThreadLocal<Node> threadLocal = new ThreadLocal<Node>() { @Override protected Node initialValue() { Node n = new Node(); return n; } }; public void lock() { Node cur = threadLocal.get(); cur.state = WAITING; Node prev = tail.getAndSet(cur); if (prev != null) { cur.prev = prev; while (prev.state == WAITING) { } } } public void unlock() { Node cur = threadLocal.get(); cur.state = RELEASED; } static class Node { Node prev; volatile int state = FREE; }}
1、空间复杂度低,O(L+n),L个锁,n个节点。
4、MCSLock
public class MCSLock implements Lock { public static final int FREE = 1; public static final int WAITING = 1 << 1; public static final int SINGAL = 1 << 2; public AtomicReference<Node> tail = new AtomicReference<Node>(); ThreadLocal<Node> mNode = new ThreadLocal<Node>() { @Override protected Node initialValue() { Node n = new Node(); return n; } }; public void lock() { Node cur = mNode.get(); Node prev = tail.getAndSet(cur); if (prev != null) { cur.state = WAITING; prev.next = cur; while (cur.state == WAITING) { } } } public void unlock() { Node cur = mNode.get(); Node next = cur.next; if (next == null) { if (tail.compareAndSet(cur, null)) { return; } while (cur.next == null) {} } next.state = SINGAL; cur.state = FREE; cur.next = null; } static class Node { Node next; volatile int state = FREE; }}MCSLock和CLHLock的区别在于,MCSLock的链表是显式的,每个节点都有next指针指向下一个节点。在获得锁的时候首先会先得到该节点的前驱节点,如果为空,则直接进入临界区。非空则将节点插入等待队列尾部,并且在当前节点的state上自旋。解锁的时候,会先判断当前节点的后继是否为空,如果为空则尝试将tail设置为空,成功则证明MCSLock的等待队列为空,可以直接退出。如果设置tail失败,则证明在这段时间内又有新的节点加入等待队列当中,继续执行下面的操作。接下来就是等待队列中有节点的情况,获得当前节点的后继节点,设置其state为SINGAL,通知其停止自旋,进入临界区执行代码。回收节点。
- 自旋队列锁
- 信号量/等待队列、 自旋锁
- java并发笔记之自旋锁、排队自旋锁、MCS队列锁、CLH队列锁
- 本地自旋锁与信号量/多服务台自旋队列-spin wait风格的信号量
- Java多线程之自旋锁与队列锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 自旋锁
- 树莓派驱动步进式电机python编程实现
- Android自定义类似ProgressDialog效果的Dialog
- 【cocos2d-js官方文档】二十五、Cocos2d-JS v3.0中的单例对象
- Linux下图形安装Oracle11g
- sgu177:Square(矩形分割)
- 自旋队列锁
- 第17周项目4-日期结构体之一
- 转载:标准c的字符串,数字相互转化的一些函数(“stdlib.h”)
- 什么是OpenGL中的深度、深度缓存、深度测试?
- java自动装箱与拆箱
- 第一篇转载
- android Vibrator 振动使用
- 二维数组的定义和使用
- DWZ简介及其使用