ReentrantLock 源码分析 - 非公平锁 lock()方法

前言

前面用了 4 篇文章为本文分析 ReentrantLock 源码做了一些铺垫,

ReentrantLock 源码分析 - 并发小知识

ReentrantLock 源码分析 - 并发基础类

ReentrantLock 源码分析 - AbstractQueuedSynchronizer 详解(一)

ReentrantLock 源码分析 - AbstractQueuedSynchronizer 详解(二)

赶紧趁热打铁,来看下 ReentrantLock 非公平锁的lock()方法是如何实现的。

正文

知识回顾

ReentrantLock 既可以实现非公平锁,也可以实现公平锁。

通过 ReentrantLock lock = ReentrantLock(false) 的方式可以创建一个非公平锁。

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

从上面的构造方法可以看出, 当传入 false 时,new 了个 NonfairSync 对象。

NonfairSync 类

通过查看源码,可以看出NonfairSync 继承 Sync

Sync 又是什么? 请往后看。

    static final class NonfairSync extends Sync {
     
    }

Sync 类

Sync 继承自我们熟悉的 AQS, 前面花了很多篇幅去介绍 本文会涉及 到的 AQS部分实现。Sync 中有一个nonfairTryAcquire(int acquire)方法,该方法在前面的文章中也举例说明过,

我们先跳过这段源码分析,接着往下看 :

   abstract static class Sync extends AbstractQueuedSynchronizer {
     
        // 知识回顾:前面有介绍 ReentrantLock 是可重入锁,state 表示重入次数
        final boolean nonfairTryAcquire(int acquires) {
           // 获取当前线程
            final Thread current = Thread.currentThread();
            // 获取 state
            int c = getState();
            // 如果 state == 0,表示锁未被占用,
            // 然后通过 CAS 操作,把 state + 1,标记锁被占用
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果锁被线程 A 占用
            // 判断当前线程是否就是线程 A
            // 如果是,就把 state +1
            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;
        }

    }

lock()

回归初心,来看下 lock 的调用方式

ReentrantLock lock = new ReentrantLock(false);
lock.lock();

而 lock() 的实现如下:

   public void lock() {
        sync.lock();
    }

根据前面讲的,此时 sync = NonfairSync(),

NonfairSynclock 方法实现如下:

       final void lock() {
           // 通过 CAS 原子操作,把 state 由 0 设置为 1
            if (compareAndSetState(0, 1))
                // 设置成功,把当前线程标记为独占锁持有者
                setExclusiveOwnerThread(Thread.currentThread());
            else
               // 如果 cas 失败,表示抢占锁失败,把当前线程放入 AQS 队列,入队后,还会有一系列操作
               // AQS 的 acquire(int arg) 方法, 上文有详细讲过
                acquire(1);
        }

tryAcquire(int arg) 方法

前面提到了 AQS 的 acquire 方法中调用了 tryAcquire方法,该方法主要目的是获取锁资源, 它本身在 AQS 中是抽象方法,由具体的子类实现的(在此处就是 NonfairSync), 而 tryAcquire 方法又调用了nonfairTryAcquire方法, 这时候我们就可以 传送Sync 类 介绍部分,去阅读那段nonfairTryAcquire方法的源码

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

ReentrantLock 的非公平性

上面只是讲了非公平锁 lock() 方法的执行流程,来看下它的非公平性体现在哪

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 执行到此方法时,获取到 state !=0 ,表示 锁已经被线程 A 占用了,
然后判断线程1是否等于 线程 A, 如果不是返回 false,然后进入队列。

在线程 1 入队的同时,线程 2 也来到此方法,然后获取到 state == 0,通过 CAS 操作拿到锁。

明明是 线程 1 先来的,但是锁却被后来的线程 2 拿到了,这明显是不公平的。

梳理一下

  • 所有的线程都是根据 state 的值来判断,锁是否被占用, state == 0,表示未被占用

  • state 的值是一直在变化的,state 被 N 多个线程在读写

  • N 多个线程来自于 两个方向 的线程,其中一波是 之前抢占锁失败被放入 AQS 队列的线程,另一波是 “新来的” (比如刚刚 start() 的一些线程)。

  • 明显前面一波线程来得更早,但是两个方向的线程却不区分先后顺序,因此“新来的”线程很可能会比,AQS 队列的线程先抢到锁。

  • 以上就是不公平的体现。

总结

可以看出本文内容非常少,我们前期花了大量的功夫去挖掘一些基础知识,因为 ReentrantLock 本身就是基于 AQS 实现的,而 AQS 又依赖于下面的小知识

ReentrantLock 源码分析 - 并发小知识

ReentrantLock 源码分析 - 并发基础类

因此把前面的基础打好,ReentrantLock的具体实现就很容易看懂了。

再次总结下本文主要内容:

  1. ReentrantLock 可以通过 new ReentrantLock(false)方式实现非公平锁
  2. 第 1 步 产生的结果就是 new 了一个 NonfairSync
  3. NonfairSync 本文继承自 Sync, 而 Sync 又继承自 AQS
  4. 当调用非公平锁的 lock()方法时,就是调用NonfairSynclock 方法
  5. 第 4 步就是一个抢占锁的过程
    • 抢占成功与否是根据 CAS 操作结果判定的
    • 抢占失败的线程会调用 acquire方法,继续尝试获取锁,获取失败就会被被放入 AQS 队列(具体策略看上一篇文章)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值