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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

文章首发于: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;
            }

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


来源:https://www.jianshu.com/p/347ea7f881f8

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 【锁】ReentrantLock之获取锁时公平性的体现与锁的释放

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏