自旋无界队列实现 上代码: public class MCSLock { private static final Logger logger = LoggerManager.getLogger(MCSLock.class); private AtomicReference<QNode> tail; ThreadLocal<QNode> myNode; public MCSLock() { tail = new AtomicReference<QNode>(null); myNode = ThreadLocal.withInitial(()->new QNode()); } public void lock() { QNode node = myNode.get(); QNode preNode = tail.getAndSet(node); if (preNode!=null) { node.lock = true; preNode.next = node; while (node.lock) { //也可以不休眠,自行考虑 try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { logger.error("锁休眠错误", e); } } } } public void unlock() { QNode node = myNode.get(); if (node.next==null) { // CAS操作,需要判断特殊情况是否新加入的节点但是next还没挂载上 if (tail.compareAndSet(node, null)) { // 没有新加入的节点,直接返回 return; } // 有新加入的节点,等待设置链关系 while (node.next==null) { } } // 通知下一个节点获取锁 node.next.lock = false; // 设置next节点为空 node.next = null; } public static class QNode { volatile boolean lock; volatile QNode next; } }
private static Map<String, MCSLock> lockMap = Collections.synchronizedMap(new HashMap<>()); public static void releaseLock(String lockKey) { MCSLock lock = geMCSLock(lockKey); JedisClient.del(lockKey); lock.unlock(); } public static void getLock(String lockKey) { MCSLock lock = geMCSLock(lockKey); lock.lock(); boolean lockFlag = true; while (lockFlag) {//循环等待拿锁 if (acquireLock(lockKey)) { lockFlag = false; } } } public static MCSLock geMCSLock(String lockKey) { MCSLock lock = lockMap.get(lockKey); if (lock==null) { synchronized (lockMap) { if (lock==null) { lock = new MCSLock(); lockMap.put(lockKey, lock); } return lock; } } return lock; }
说明:
1.每个请求在获取锁时,会进队列,并在本节点上本地变量自旋
2.在获取redis锁时先自旋,实际是能保证一个请求只会进行一次setnx,当然在多节点下,存在不是一次的情况,即在本节点获取到线程锁了,但是没获取到redis锁,这时候会进行redis锁的自旋,一个节点只会存在一个请求的redis自旋,实际性能是能接受的
3.每个类型的业务一个锁,比如我这里就是一个活动一个本地锁
4.这里没有考虑死锁、锁超时等问题还有继续优化的空间,虽说不是常发生这样的问题,但发生一次也挺头疼