- TicketSpinLock
- CLHSpinLock
- MCSSpinLock
# 自旋锁spinlock
自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。
SimpleSpinLock里有一个owner属性持有锁当前拥有者的线程的引用,如果该引用为null,则表示锁未被占用,不为null则被占用。
这里用AtomicReference是为了使用它的原子性的compareAndSet方法(CAS操作),
解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态。
缺点
CAS操作需要硬件的配合;
保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重;
没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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); } }
|
TicketSpinLock
Ticket Lock 是为了解决上面的公平性问题,类似于现实中银行柜台的排队叫号:
锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,
然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。
当线程释放锁时,将服务号加1,这样下一个线程看到这个变化,就退出自旋。
Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,
每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class TicketSpinLock { 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); } }
|
CLHSpinLock
CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,
它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
*
差异:
*
从代码实现来看,CLH比MCS要简单得多。
从自旋的条件来看,CLH是在本地变量上自旋,MCS是自旋在其他对象的属性。
从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。
注意:这里实现的锁都是独占的,且不能重入的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class CLHSpinLock { public static class CLHNode { private boolean isLocked = true; }
@SuppressWarnings("unused") private volatile CLHNode tail; private static final AtomicReferenceFieldUpdater<CLHSpinLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater .newUpdater(CLHSpinLock.class, CLHNode.class, "tail");
public void lock(CLHNode currentThread) { CLHNode preNode = UPDATER.getAndSet(this, currentThread); if (preNode != null) { while (preNode.isLocked) { } } }
public void unlock(CLHNode currentThread) { if (!UPDATER.compareAndSet(this, currentThread, null)) { currentThread.isLocked = false; } } }
|
MCSSpinLock
MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class MCSSpinLock { public static class MCSNode { volatile MCSNode next; volatile boolean isLocked = true; }
volatile MCSNode queue; private static final AtomicReferenceFieldUpdater<MCSSpinLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater .newUpdater(MCSSpinLock.class, MCSNode.class, "queue");
public void lock(MCSNode currentThread) { MCSNode predecessor = UPDATER.getAndSet(this, currentThread); if (predecessor != null) { predecessor.next = currentThread;
while (currentThread.isLocked) { } } }
public void unlock(MCSNode currentThread) { if (UPDATER.get(this) == currentThread) { if (currentThread.next == null) { if (UPDATER.compareAndSet(this, currentThread, null)) { return; } else { while (currentThread.next == null) { } } }
currentThread.next.isLocked = false; currentThread.next = null; } } }
|