小白的 ReentrantLock 源码略谈

开门见山,ReentrantLock 简单介绍

ReentrantLock(简称RLock) 是Java的一种锁机制。从API上看,RLock提供了公平锁与非公平锁,并提供了当前锁状态监测的一些接口。其内部是由 FairSyncNonFairSync 来实现锁资源的抢占与释放。下面我们来学习下其源码。

(想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

从最直白的方法入手

首先我们打开 RLock 的构造函数,源码如下:

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

好的,很直白,根据入参构造成员变量 Sync。默认为 NonfairSync。到这里,我们遇到了第一个新概念 Sync 。先按住好奇心,我们先找到 lock()unlock() 源码。如下:

  public void lock() {
        sync.lock();
    }
    public void unlock() {
        sync.release(1);
    }

到这里好像有些明白了,RLock更像是对Sync的进一层封装,通过多态来实现不同的锁策略。到这里,我有个疑问,公平锁和非公平锁的策略有何不同呢?那么这样看来,啃透Sync是关键。

居高观下

首先我们捋一下 Sync 继承结构。IDEA里鼠标移到类声明上,Ctrl+H即可清晰看到类的继承结构。
在这里插入图片描述
这里我们看到了一个熟悉的老朋友-- AbstractQueuedSynchronizer(简称AQS)。这个类是JDK并发包中的锁基类,定义了锁资源获取与释放的框架与基本行为。这个先略过不谈,继续贯彻第一步,从最直白的方法入手。

lock()

我们回忆一下lock的行为,我们调用来获取锁,如果其它线程已抢占到锁资源,当前线程挂起,直到当前线程获取到锁。而且RLock支持重入。
NonfairSync#lock()FairSync#lock() 源码如下:
在这里插入图片描述
从第4,5,6行可以看到,NonfairSync 先设置了状态位,然后调用了acquire()FairSync 则直接调用了 acquire()。那么我们先从 compareAndSetState() 入手,源码如下:

 protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

这个函数定义在 AQS 中,用于设置当前lock的状态,unsafe呢,是JDK中非常变态的一个工具类,可以直接操控实例对象所在的内存,同时提供了一些原子操作。具体的后面会再展开介绍,简而言之,这个函数可以理解为:

protected final boolean compareAndSetState(int expect, int update) {
        synchronized (this.getClass()) {
            if (this.state == expect) {
                this.state = update;
                return true;
            } else {
                return false;
            }
        }
    }

那么到这里我们好像得到了第一把钥匙:lock.state 为0时,为空闲,而上锁请求会将状态置为1,并且将exclusiveOwnerThread设为当前线程。

接下来,我们来看 acquire()。继续跟踪下去, 的 acquire() 代码如下:

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

唔,好像看不出来什么东西,那么继续跟下去,先从 tryAcquire 开始,NonfairSyncFairSync tryAcquire() 核心代码如下
在这里插入图片描述
唯一的差别在于,FairSync 获取锁之前会调用 hasQueuedPredecessors() 源码如下:

 /**
     * @return {@code true} if there is a queued thread preceding the
     *         current thread, and {@code false} if the current thread
     *         is at the head of the queue or the queue is empty
     *  两种情况返回 false:1. 当前线程在队头。2. 队列为空
     */
    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());
    }

那么我们拿到了第二把钥匙: 公平锁在获取锁之前会先去查询是否有其他人在等待这把锁,如果没有,再尝试获取。而非公平锁则不会询问

这么看来,公平锁 Peace&Love,像民谣,多愁善感,与世无争。而非公平锁 Aggressive,像Hip-Hop,张扬自我,锐意进取。
那么回到 acquire() ,还有一个函数: acquireQueued(),源码如下:

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

忽略其他细节,我们在 parkAndCheckInterrupt() 函数中找到了 LockSupport.park(this);。就是他了!这个函数会将指定线程挂起,直至LockSupport.unpark(Thread)被调用或者发生意外被操作系统 interrupt
至此, lock() 的链便通了。

我们再来总结一下:***ReentrantLock在上锁时,会根据实例化时指定的策略去获取锁,默认为非公平锁。如果上锁成功,锁状态值+1(重入,最大次数为 Integer.MAX_VALUE),并将锁持有者设置为当前线程实例。在 Sync 内部维护了一个队列,存放了所有上锁失败的线程。公平锁在上锁前,会检查在自己前面是否还有其他线程等待,如果有就放弃竞争,继续等待。而非公平锁会抓住每个机会,不管是否前面是否还有其它线程等待,只顾上锁***

unlock()

至于锁释放,公平锁与非公平锁的行为就一样了。核心代码如下

   // ReentrantLock#unlock() 释放锁资源
    public void unlock() {
        sync.release(1);
    }
    // Sync#release()
    public final boolean release(int arg) {
        // 重入锁,状态计数器减一,为0时释放
        if (tryRelease(arg)) { 
            Node h = head;
            // 释放锁时,从等待队列中获取线程并尝试唤醒
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    // Sync#tryRelease() 状态计数器减一,为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;
    }
    // Sync#unparkSuccessor()  唤醒等待队列中的线程,让他(们)继续抢占锁
    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        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;
        }
        // boomya,合适的线程找到啦,将其唤醒
        if (s != null)
            LockSupport.unpark(s.thread);
    }

总结一下: 线程在释放锁时,将状态计数器减一(重入),当状态计数器为0时,锁可用。此时再从等待队列中寻找合适的线程唤醒,默认从队首开始,如果队列正在更新中,且未找到合适的线程,那么从队尾开始寻找。

重复的总结,就知道你喜欢看结论

  • ReentrantLock在上锁时,会根据实例化时指定的策略去获取锁,默认为非公平锁。如果上锁成功,锁状态值+1(重入,最大次数为 Integer.MAX_VALUE),并将锁持有者设置为当前线程实例。在 Sync 内部维护了一个队列,存放了所有上锁失败的线程。公平锁在上锁前,会检查在自己前面是否还有其他线程等待,如果有就放弃竞争,继续等待。而非公平锁会抓住每个机会,不管是否前面是否还有其它线程等待,只顾上锁
  • ReetrantLock在释放锁时,将状态计数器减一(重入),当状态计数器为0时,锁可用。此时再从等待队列中寻找合适的线程唤醒,默认从队首开始,如果队列正在更新中,且未找到合适的线程,那么从队尾开始寻找。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值