从构建分布式秒杀系统聊聊Lock锁


前言

一个月之前就备好了标题,然后就没有然后了,以至于zk的分布式锁都收尾了,这里还是单单的一只图片。都说自律的人最可怕,我这不自律的让自己都害怕,趁着周末,赶紧撸一发。

案例

在秒杀案例中,我们使用ReentrantLock来保证商品不会被超卖,以下是伪代码:

private Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁

@Override
@Transactional
public Result  startSeckilLock(long seckillId, long userId) {
    try {
        lock.lock();
        //业务代码逻辑
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
       lock.unlock();
    }
    return Result.ok(SeckillStatEnum.SUCCESS);
}

不过由于经验不足,这个地方曾踩过一个大坑,详见:从构建分布式秒杀系统聊聊Lock锁使用中的坑。有兴趣的小伙伴可以了解一下,事务提交和锁释放的顺序导致超卖的问题。

介绍

参考JDK8官方文档给的解释,ReentrantLock具有和synchronized相同的基本行为和语义的可重入互斥,但具有扩展功能。

  • 具有非阻塞锁特性,超时获取锁失败
  • 提供公平锁、非公平锁参数

ReentrantLock的实现

创建锁:

/**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        //默认为false非公平锁
        sync = fair ? new FairSync() : new NonfairSync();
    }

所谓公平锁,就是按照时间先后顺序,即先等待的线程先得到锁。而且,公平锁不会产生饥饿锁,也就是只要排队等待,最终能等待到获取锁的机会。

公平锁:

    /**
     *  继承Sync,后面会提到
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
           //调用AbstractQueuedSynchronizer的模板方法acquire
           acquire会调用下面我们重写的这个tryAcquire
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取同步锁状态,volatile修饰保证其可见性
            int c = getState();
            //判断是否
            if (c == 0) {
               //检查在时间顺序上,是否有比自己早的线程,CAS设置state(基于unsafe底层)获取锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //重入锁逻辑,当前线程已经获取
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

获取锁:

public final void acquire(int arg) {
     if (!tryAcquire(arg) &&        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          selfInterrupt();
}

自旋获取锁:

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

入队列addWaiter,新的节点从队尾插入,node的模式是Node.EXCLUSIVE,使用compareAndSetTail保证原子性,只有没获取到锁的线程才会入队列。

 private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

非公平锁:

获取锁:

参考

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html

qrcode_for_gh_bf7a27ade681_258.jpg

作者: 小柒

出处: https://blog.52itstyle.com

分享是快乐的,也见证了个人成长历程,文章大多都是工作经验总结以及平时学习积累,基于自身认知不足之处在所难免,也请大家指正,共同进步。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 如有问题, 可邮件(345849402@qq.com)咨询。