ReentrantLock 公平锁和非公平锁加锁和解锁源码分析(简述)

本文详细分析了ReentrantLock的公平锁和非公平锁在加锁与解锁过程中的源码实现。从构造函数开始,通过调用lock()方法进入加锁流程,非公平锁尝试使用CAS快速获取锁,失败则进入队列等待。在解锁时,调用release()方法,尝试释放锁,并唤醒后续等待的线程。公平锁在尝试获取锁时,会检查队列中是否有更早的线程,遵循先来后到原则。关键词包括锁机制、并发控制、AQS、CAS操作和线程同步。
- title: ReentrantLock 公平锁和非公平锁加锁和解锁源码分析(简述)
- date: 2021/8/16

一、ReentrantLock

ReentantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完成
synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法

1. 构造函数

    private final Sync sync;
    //...
    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * 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) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

无参和有参构造函数,用于构造公平锁或非公平锁。

二、ReentrantLock 公平锁的加锁(lock)过程

参考示例代码:

class ReentrantLockExample {
    int           a    = 0;
    ReentrantLock lock = new ReentrantLock();

    public void writer() {
        lock.lock(); //获取锁
        try {
            a++;
        } finally {
            lock.unlock(); //释放锁
        }
    }

    public void reader() {
        lock.lock(); //获取锁
        try {
            int i = a;
            //……
        } finally {
            lock.unlock(); //释放锁
        }
    }
}

1.1 调用ReentrantLock中的lock

//ReentrantLock.java ReentrantLock
    /**
     * Acquires the lock.
     *
     * <p>Acquires the lock if it is not held by another thread and returns
     * immediately, setting the lock hold count to one.
     *
     * <p>If the current thread already holds the lock then the hold
     * count is incremented by one and the method returns immediately.
     *
     * <p>If the lock is held by another thread then the
     * current thread becomes disabled for thread scheduling
     * purposes and lies dormant until the lock has been acquired,
     * at which time the lock hold count is set to one.
     */
     /**
      * 获取锁。
      *
      * <p>如果没有被其他线程持有则获取锁并返回
      * 立即,将锁定保持计数设置为 1。
      *
      * <p>如果当前线程已经持有锁,则持有
      * count 加一,方法立即返回。
      *
      * <p>如果锁被另一个线程持有,那么
      * 当前线程被禁用以进行线程调度
      * 目的并处于休眠状态,直到获得锁,
      * 此时锁定保持计数设置为 1。
      */
    public void lock() {
        sync.lock();
    }

1.2 调用Sync中的抽象方法lock,具体实现由子类完成,这里是非公平锁

//ReentrantLock.java Sync
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

1.3 调用NonfairSync中的 lock;

//ReentrantLock.java NonfairSync
    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            //首先尝试进行CAS操作,将state值从0改为1
            if (compareAndSetState(0, 1))
                //如果CAS成功,设置exclusiveOwnerThread为当前变量
                //获取锁成功说明:1.state从0变为1;(从无竞争资源变为独占资源) 
                //2. 此锁的exclusiveOwnerThread 为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //如果CAS失败,说明有其它的线程在抢占这个锁资源
                acquire(1);
        }

//AbstractOwnableSynchronizer.java AbstractOwnableSynchronizer   
    
    /**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * Sets the thread that currently owns exclusive access.
     * A {@code null} argument indicates that no thread owns access.
     * This method does not otherwise impose any synchronization or
     * {@code volatile} field accesses.
     * @param thread the owner thread
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
        
 //AbstractQueuedSynchronizer.java AbstractQueuedSynchronizer
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • 首先执行 compareAndSetState(0, 1) ,尝试进行CAS操作,将state值从0改为1;

    • 如果CAS成功,调用 setExclusiveOwnerThread(Thread.currentThread()) ,设置exclusiveOwnerThread为当前变量;

      获取锁成功说明:

      1. state从0变为1;(从无竞争资源变为独占资源) ;

      2. 此锁的 exclusiveOwnerThread 为当前线程

    • 如果CAS失败,说明有其它的线程在抢占这个锁资源,执行acquire(1);

1.3.1 acquire 方法:

 //AbstractQueuedSynchronizer.java AbstractQueuedSynchronizer
    /**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
1.3.1.1 tryAcquire
//ReentranLock.java NonFairSync
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            int c = getState();
            //如果c是0,锁处于空闲状态,使用CAS获取
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    //如果CAS成功,设置当前线程是锁的持有者
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果当前线程是锁的持有者,这是ReentrantLock可重入的原因;
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                //可以看出源码设计者的周到,考虑到了锁溢出的情况
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //将当前线程持有的锁+1
                setState(nextc);
                return true;
            }
            return false;
        }
1.3.1.2 acquireQueued
1.3.1.2.1 addWaiter()

addWaiter():将当前线程插入至队尾,返回在等待队列中的节点(就是处理了它的前驱后继)。

    private Node addWaiter(Node mode) {
        //把当前线程封装为node,指定资源访问模式
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //如果tail不为空,把node插入末尾
        if (pred != null) {
            node.prev = pred;
            //此时可能有其他线程插入,所以使用CAS重新判断tail
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果tail为空,说明队列还没有初始化,执行enq()
        enq(node);
        return node;
    }

enq():将节点插入队尾,失败则自旋,直到成功。

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //虽然tail==null才会执行本方法
            //但是可能刚好有其他线程插入,会导致
            //之前的判断失效,所以重新判断tail是否为空
            //队尾为空,说明队列中没有节点
            //初始化头尾节点
            if (t == null) { 
                if (compareAndSetHead(new Node()))
                    //初始化完成后,接着走下一个循环,
                    //直到node正常插入尾部
                    tail = head;
            } else {
                //下面就是链表的正常插入操作了
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
1.3.1.2.2 acquireQueued:

该函数表示将已经在队列中的node(每个线程对应一个node加到等待队列中,具体以后分析)尝试去获取锁否则挂起

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //自旋
                final Node p = node.predecessor(); //获得该node
        /*
         * 如果前置节点是head,表示之前的节点就是正在运行的线程,表示是第一个排队的
         * (一般讲队列中第一个是正在处理的,可以想象买票的过程,第一个人是正在买票(处理中),第二个才是真正排队的人);
         * 那么再去tryAcquire尝试获取锁,如果获取成功,说明此时前置线程已经运行结束,则将head设置为当前节点返回
         */
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    // help GC,将前置节点移出队列,这样就没有指针指向它,可以被gc回收
                    p.next = null; // help GC
                    failed = false;
                    //返回false表示不能被打断,意思是没有被挂起,也就是获得到了锁
                    return interrupted;
                }
        /* shouldParkAfterFailedAcquire将前置node设置为需要被挂起,
         *注意这里的waitStatus是针对当前节点来说的,
         * 即是前置node的ws指的是下一个节点的状态
         */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
1.3.1.2.2.1 shouldParkAfterFailedAcquire()

shouldParkAfterFailedAcquire():判断当前节点是否应该被挂起。下面涉及到的等待状态,这里再回忆一下,CANCELLED =1,SIGNAL =-1,CONDITION = -2,PROPAGATE = -3,0

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //前驱节点的状态是SIGNAL,说明前驱节点释放资源后会通知自己
            //此时当前节点可以安全的park(),因此返回true
            return true;
        if (ws > 0) {
            //前驱节点的状态是CANCLLED,说明前置节点已经放弃获取资源了
            //此时一直往前找,直到找到最近的一个处于正常等待状态的节点
            //并排在它后面,返回false
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //前驱节点的状态是0或PROPGATE,则利用CAS将前置节点的状态置
            //为SIGNAL,让它释放资源后通知自己
            //如果前置节点刚释放资源,状态就不是SIGNAL了,这时就会失败
            // 返回false
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt():若确定有必要park,才会执行此方法。

    private final boolean parkAndCheckInterrupt() {
        //使用LockSupport,挂起当前线程
        LockSupport.park(this);
        return Thread.interrupted();
    }
1.3.1.3 selfInterrupt

selfInterrupt():对当前线程产生一个中断请求。能走到这个方法,说明acquireQueued()返回true,就进行自我中断。

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

到这里,获取资源的流程就走完了。

##1.4 获取非公平锁总结

  1. 在NonSync中的lock函数CAS尝试获取锁;

  2. 如果CAS失败,**tryAcquire()**尝试获取资源,该函数是一个钩子函数,在非公平锁中是nonfairTryAcquire();

    在nonfairTryAcquire中,会如果当前锁的状态是空闲的,进行CAS尝试获取锁,如果成功返回true,失败返回false; 否则,判断当前线程是否拥有该锁,进行一个可重入的操作,否则返回false;

  3. 如果tryAcquire返回false, 调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),首先执行的是addWaiter函数;

    在addWaiter中,将当前线程插入至队尾(CAS操作),返回在等待队列中的节点(就是处理了它的前驱后继),如果tail为空或CAS失败,执行enq():将节点插入队尾,失败则自旋,直到成功。

    然后执行acquireQueued函数,进行一个自旋操作,获取前面的节点,判断是否是头节点head,如果是调用tryAcquire函数再次进行CAS获取锁和可重入的判断,如果成功,然后help GC,将前置节点移出队列,返回interrupted;否则,执行shouldParkAfterFailedAcquire(pred,node),如果返回true,再执行park,如果返回true,interrupted = true,继续自旋;

    shouldParkAfterFailedAcquire(pred,node):

    首先获取前驱节点的状态,

    如果前驱节点的状态是SIGNAL,说明前驱节点释放资源后会通知自己,此时当前节点可以安全的park(),因此返回true;

    如果前驱节点的状态是CANCLLED(1),说明前置节点已经放弃获取资源了,此时一直往前找,直到找到最近的一个处于正常等待状态的节点,将当前节点并排在它后面,返回false;

    如果前驱节点的状态是0或PROPGATE,则利用CAS将前置节点的状态置为SIGNAL,让它释放资源后通知自己,然后返回false;

    parkAndCheckInterrupt():若确定有必要park,才会执行此方法。使用LockSupport,挂起当前线程,并会返回当前线程是否处于中断状态;

  4. 如果acquireQueued返回true,执行selfInterrupt():对当前线程产生一个中断请求。能走到这个方法,说明acquireQueued()返回true,就进行自我中断。

就此,加锁过程完成;

三、ReentrantLock 非公平锁和非公平锁的解锁(unlock)过程

二者的解锁是相同的;

    public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

这个tryRelease也是一个钩子函数:尝试释放锁,彻底释放后返回true

//ReentrantLock.java ReentrantLock
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //释放锁必须保证当前线程是锁的持有者
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //如果释放后状态值为0,则彻底释放,持有者置空
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

在释放锁的最后写volatile变量 state;

unparkSuccessor():尝试找到下一位继承人,就是确定下一个获取资源的线程,唤醒指定节点的后继节点。

    private void unparkSuccessor(Node node) {
        //如果状态为负说明是除CANCEL以外的状态,
        //尝试在等待信号时清除。
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        //下一个节点为空或CANCELLED状态
        //则从队尾往前找,找到正常状态的节点作为之后的继承人
        //也就是下一个能拿到资源的节点
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //此时找到继承人了,那么就唤醒它
        if (s != null)
            LockSupport.unpark(s.thread);
    }

四、ReentrantLock 公平锁的加锁(lock)过程

//ReentrantLock.java FairLock
       final void lock() {
            acquire(1);
        }

公平锁和非公平锁获取锁只有在钩子函数tryAcquire的实现不同,和非公平锁相比,增加了一个判断当前节点是否是头节点;

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //判断等待队列中是否有前驱节点,没有则尝试获取锁
                //hasQueuedPredecessors()返回false,表示没有前驱节点,当前线程就是头节点
                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;
        }

boolean hasQueuedPredecessors():判断当前线程是否位于CLH同步队列中的第一个。如果是则返回flase,否则返回true。

    public final boolean hasQueuedPredecessors() {
        //判断当前节点在等待队列中是否为头节点的后继节点(头节点不存储数据),
        //如果不是,则说明有线程比当前线程更早的请求资源,
        //根据公平性,当前线程请求资源失败。
        //如果当前节点没有前驱节点的话,才有做后面的逻辑判断的必要性
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

参考连接:

  1. AQS源码详细解读 - 知乎 (zhihu.com)
  2. ReentrantLock源码详细解读 - 知乎 (zhihu.com)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值