【锁】ReentrantLock之获取锁时公平性的体现与锁的释放

文章首发于:clawhub.club


公平锁实现了先进先出的公平性,但是由于来一个线程就加入队列中,往往都需要阻塞,再由阻塞变为运行,这种上下文切换非常资源。
非公平锁由于允许插队,所以上下文切换少,能保证的大的吞吐量,但是容易出现饥饿问题。

查看ReentrantLock源码,其内部有三个重要的内部类:
Sync继承自AbstractQueueSynchronizer同步器,这个锁的同步操作由这个Sync来保证。
NofairSync非公平同步器,继承Sync,在同步的基础上,保证线程获取锁是非公平的。
FairSync公平同步器,继承Sync,公平锁的体现。

ReentrantLock默认初始化为非公平锁。

在执行ReentrantLock的Lock方法的时候,会调用Sync的Lock方法,最后由NofairSync或者FairSync实现。

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

首先看NonfairSync

 /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         * 执行获取锁的操作,尝试获取锁,如果失败的话,进入同步队列等待。
         */
        final void lock() {
            //CAS尝试获取锁。
            if (compareAndSetState(0, 1))
                //如果获取锁成功的话,将自己放在当前持有锁的线程位置。
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //获取锁失败,直接入同步队列,这块就和公平锁一样了。
                acquire(1);
        }

非公平锁在执行获取锁操作的时候,会先尝试的获取一下锁,即和同步队列中等待的线程竞争,如果获取失败的时候才会入同步队列,
这步操作即减少了入队、阻塞、出队的操作,能增大锁处理的吞吐量,但是也有可能同步队列中的等待线程迟迟不能获取到锁,因为都被新来的抢占了,发生饥饿现象。
源码中,当前获取锁失败之后会调用AQS中定义的acquire方法,最后又会回调本类中的tryAcquire(int acquires)方法:

  /**
         * 由父类回调,尝试获取锁
         * @param acquires
         * @return
         */
        protected final boolean tryAcquire(int acquires) {
            //非公平方式获取
            return nonfairTryAcquire(acquires);
        }

而nonfairTryAcquire方法的逻辑功能因为ReentrantLock的tryLock()方法与非公平同步器都需要使用,所以提到Sync中实现:

   /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         * 执行非公共方式获取锁,按理说应该子类实现,但是ReentrantLock的tryLock()方法也需要这块的逻辑,所以提到了上层处理。
         */
        final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取当前锁状态
            int c = getState();
            //如果锁状态为0,即没有线程持有当前锁
            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;
        }

这段代码中就体现了ReentrantLock的可重入性,在当前线程再次获取锁的时候,发现获取失败了,而且当前持有锁的线程正是自己,
那就将锁的重入次数增加,减少了再次获取锁的消耗。

再来看FairSync

  /**
         * 获取锁
         */
        final void lock() {
            //直接调用AQS的方法,最后会回调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();
            //获取当前锁的状态,即AQS中的state属性
            int c = getState();
            //c==0即锁没有被占用
            if (c == 0) {

                //首先检查自己是不是处于头节点的后继节点,即队列中有没有排在我前面的节点
                //之后将state设置为1
                if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {
                    //上面两步都成功之后,将AbstractOwnableSynchronizer的exclusiveOwnerThread设置为当前线程,就此获取锁成功。
                    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;
        }

源码中,没有了非公平锁的一进来就和同步队列中的等待线程竞争,而是直接进入同步队列,接下来就是AQS的问题了,以前分析过就不写了。

直接将锁的释放也分析一下:

/**
         * 释放锁执行操作,因为想要释放锁,肯定是持有锁,所以此方法内部不需要同步方法。
         * @param releases 释放次数
         * @return
         */
        protected final boolean tryRelease(int releases) {
            //获取释放后当前锁被重入次数
            int c = getState() - releases;
            //为什么还要判断一下当前线程是不是获取锁的线程呢?
            //可能是:如果有线程没有调用Lock但是先效用Unlock的时候,做一个判断。
            //为什么不把这句判断放到  int c = getState() - releases; 之前呢,即减少一次锁重入次数的计算?
            // 不知道为啥。。欢迎有想法的小伙伴给我些提示
           if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();

           //设置个锁标志位
            boolean free = false;
            //只有当c为0时,此锁才不被线程持有
            if (c == 0) {
                free = true;
                //清掉线程位
                setExclusiveOwnerThread(null);
            }
            //设置锁状态
            setState(c);
            return free;
        }

这里面有两个疑问,目前只能瞎猜。

你可能感兴趣的:(【锁】ReentrantLock之获取锁时公平性的体现与锁的释放)