zoukankan      html  css  js  c++  java
  • ReentrantLock源码分析

    ReentrantLock是一个可重入的独占锁,内部使用AQS实现。state记录着持有锁的线程的进入同步代码块的次数。

    1. AQS的基本实现Sync

    Sync是一个抽象类,他没有重写tryAcquire方法,但是他多了一个nonfairTryAcquire方法,该方法是一个非公平获取锁资源的方法。提供了一个抽象的lock方法,这是一个统一的加锁方法。

    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();
        /**
         * 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();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
        // Methods relayed from outer class
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
        final boolean isLocked() {
            return getState() != 0;
        }
        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
    

    1.1 nonfairTryAcquire

    因为在ReentrantLock是支持公平锁和非公平锁的,这里的nonfairTryAcquire方法就是非公平获取锁资源的实现。我们可以看到分两种情况,一是当没有线程持有锁时,直接cas修改state,修改成功就获取到了锁,并将当前持有独占锁的线程设置为自己。二是判断当前自己是不是持有锁的线程(可能是重入),如果是就只需增加锁的重入次数。最后就是有其他线程占用了锁,返回false。

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    

    1.2 tryRelease

    进行锁资源的释放,该方法是线程安全的,因为独占模式只有一个线程只有锁,也就只有一个线程可以释放锁资源。释放锁刚好更获取锁资源反着来,减少锁的重入次数,设置当前持有锁的线程为null,当state为0时,也就说明当前线程释放了锁资源,就返回true,从而唤醒后继节点。

    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    

    2. 公平和非公平模式

    可以通过有参构造方法,指定使用公平锁还是非公平锁,默认无参构造方法是非公平锁。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

    2.1 非公平模式

    实现了上面我们说的Sync类型,补上对应两个需要实现的方法locktryAcquire,这里的tryAcquire直接调用我们上面说的非公平获取锁资源的方法nonfairTryAcquire。这里lock方法本来可以直接调用acquire方法,但是他在前面还是做了一次快速获取锁资源的尝试,直接casstate,如果成功就设置持有锁的线程是当前线程,如果失败就再走一次正常的逻辑。

    非公平性:代码我们可以看到,主要线程获取到了锁资源,就表示该线程持有了锁,不关系队列中释放有排在其前面的线程在等待。优点很明显,就是吞吐高;缺点也很明显,会导致线程饥渴,一直等待无法获取到锁资源(因为会被其他线程抢)。

    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() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    

    2.2 公平模式

    也是实现了locktryAcquire方法,这里的lock方法就有了一点变化了,没有在lock里面之前获取锁资源了,而是全部交给了acquire来处理,这体现了公平性的一点,因为如果直接跟上面的非公平锁中lock一样获取锁资源的话,就没办法办证公平性了。tryAcquire中相比非公平锁,多了hasQueuedPredecessors方法的判断,该方法用来判断当前节点是否有前驱节点,如果有,就获取锁失败,也就是说来获取锁资源的线程必须保证他没有前驱节点,其他代码基本和非公平锁一样。

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {
            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();
            int c = getState();
            if (c == 0) {
                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 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());
    }
    

    3. 实现了lock类

    实现了统一的lock接口,我们在使用时,一般是使用lock接口中的方法进行加锁和解锁。这里的sync就是构造方法中创建的NonfairSyncFairSync,另外newCondition是创建一个条件队列对象。

    public class ReentrantLock implements Lock, java.io.Serializable {
        ...
        public void lock() {
            sync.lock();
        }
        public void lockInterruptibly() throws InterruptedException {
           sync.acquireInterruptibly(1);
        }
        public boolean tryLock() {
          return sync.nonfairTryAcquire(1);
        }
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }
        public void unlock() {
            sync.release(1);
        }
        public Condition newCondition() {
            return sync.newCondition();
        }
    }    
    

    4. 总结

    ReentrantLock其实很简单,如果你看过AQS的源码分析,那ReentrantLock就是基于AQS实现一种独占(排他)可重入的锁。他提供了公平和非公平模式,公平性是在获取锁资源时,检查当前节点是否有前驱节点。最后是实现了标准的Lock接口,方便使用统一的lockunlock进行加锁和解锁。

    GitHub:https://github.com/godfunc
    博客园:http://www.cnblogs.com/godfunc
    Copyright ©2019 Godfunc
  • 相关阅读:
    伯努利数学习笔记
    贝尔数学习笔记
    LuoguP5075 [JSOI2012]分零食
    LuoguP5748 集合划分计数
    LuoguP3338 [ZJOI2014]力
    LuoguP5488 差分与前缀和
    BZOJ4833 [Lydsy1704月赛]最小公倍佩尔数
    FFT&NTT学习笔记
    csp2019游记
    与图论的邂逅09:树上启发式合并
  • 原文地址:https://www.cnblogs.com/Godfunc/p/15210537.html
Copyright © 2011-2022 走看看