2.自旋锁的其他种类

上篇我们讲到了自旋锁,在自旋锁中 另有三种常见的锁形式:TicketLock ,CLHlock 和MCSlock

  1. package com.alipay.titan.dcc.dal.entity;
  2. import java.util.concurrent.atomic.AtomicInteger;
  3. public class TicketLock {
  4. private AtomicInteger serviceNum = new AtomicInteger();
  5. private AtomicInteger ticketNum = new AtomicInteger();
  6. private static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();
  7. public void lock() {
  8. int myticket = ticketNum.getAndIncrement();
  9. LOCAL.set(myticket);
  10. while (myticket != serviceNum.get()) {
  11. }
  12. }
  13. public void unlock() {
  14. int myticket = LOCAL.get();
  15. serviceNum.compareAndSet(myticket, myticket + 1);
  16. }
  17. }

每次都要查询一个serviceNum 服务号,影响性能(必须要到主内存读取,并阻止其他cpu修改)。
CLHLock 和MCSLock 则是两种类型相似的公平锁,采用链表的形式进行排序,

  1. import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
  2. public class CLHLock {
  3. public static class CLHNode {
  4. private volatile boolean isLocked = true;
  5. }
  6. @SuppressWarnings("unused")
  7. private volatile CLHNode tail;
  8. private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
  9. private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,
  10. CLHNode.class, "tail");
  11. public void lock() {
  12. CLHNode node = new CLHNode();
  13. LOCAL.set(node);
  14. CLHNode preNode = UPDATER.getAndSet(this, node);
  15. if (preNode != null) {
  16. while (preNode.isLocked) {
  17. }
  18. preNode = null;
  19. LOCAL.set(node);
  20. }
  21. }
  22. public void unlock() {
  23. CLHNode node = LOCAL.get();
  24. if (!UPDATER.compareAndSet(this, node, null)) {
  25. node.isLocked = false;
  26. }
  27. node = null;
  28. }
  29. }

CLHlock是不停的查询前驱变量, 导致不适合在NUMA 架构下使用(在这种结构下,每个线程分布在不同的物理内存区域)
MCSLock则是对本地变量的节点进行循环。不存在CLHlock 的问题。

  1. import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
  2. public class MCSLock {
  3. public static class MCSNode {
  4. volatile MCSNode next;
  5. volatile boolean isLocked = true;
  6. }
  7. private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
  8. @SuppressWarnings("unused")
  9. private volatile MCSNode queue;
  10. private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,
  11. MCSNode.class, "queue");
  12. public void lock() {
  13. MCSNode currentNode = new MCSNode();
  14. NODE.set(currentNode);
  15. MCSNode preNode = UPDATER.getAndSet(this, currentNode);
  16. if (preNode != null) {
  17. preNode.next = currentNode;
  18. while (currentNode.isLocked) {
  19. }
  20. }
  21. }
  22. public void unlock() {
  23. MCSNode currentNode = NODE.get();
  24. if (currentNode.next == null) {
  25. if (UPDATER.compareAndSet(this, currentNode, null)) {
  26. } else {
  27. while (currentNode.next == null) {
  28. }
  29. }
  30. } else {
  31. currentNode.next.isLocked = false;
  32. currentNode.next = null;
  33. }
  34. }
  35. }

从代码上 看,CLH 要比 MCS 更简单,
CLH 的队列是隐式的队列,没有真实的后继结点属性。
MCS 的队列是显式的队列,有真实的后继结点属性。
JUC ReentrantLock 默认内部使用的锁 即是 CLH锁(有很多改进的地方,将自旋锁换成了阻塞锁等等)。