欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JUC--ReenTrantLock学习(二)源码分析之获取锁

程序员文章站 2024-03-24 19:21:04
...

1 概述

前面我们学习了ReentrantLock的用法以及与synchronized的比较(详情请参考这篇文章:JUC--ReenTrantLock学习(一)简介与使用),同时也对AQS有了初步掌握,我们知道AQS是JUC里面大部分工具实现的基础类,并且使用了模板方法模式了实现JUC工具类。现在我们就开始对ReenTrantLock的源码进行学习。

首先我们还是来看一下ReentrantLock类的UML图。

JUC--ReenTrantLock学习(二)源码分析之获取锁

2 构造函数

我们知道针对ReentrantLock有公平锁和非公平锁,两种获取锁的方式,那么这两种方式在构造函数中又是怎样体现出来的呢?

从UML图我们可以猜想出ReentrantLock是依靠FairSync和NonfairSync来实现公平锁和非公平锁的,构造函数源码如下:

    /**
     * 默认构造函数,默认为非公平锁
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * 允许指定锁类型的构造函数
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

从上面我们可以看出构造函数默认为构造非公平锁,为什么呢?因为非公平锁的效率较公平锁高。

3  获取锁

3.1 lock普通获取锁

针对获取锁我们首先来理一下方法调用的顺序。

JUC--ReenTrantLock学习(二)源码分析之获取锁

这里我们首先来看非公平锁获取锁的操作。

3.1.1  非公平锁获取锁

final void lock() {
            
            //尝试获取锁
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            
            //获取失败则调用AQS的模板方法acquire
            else
                acquire(1);
        }

调用AQS的acquire方法。

/**
 * 这是一个模板方法,针对基本方法的实现在子类中
 */
public final void acquire(int arg) {

        //调用基本方法获取锁,如果获取失败则加入同步状态等待队列,
        //进行自旋等待,直到获取到同步状态。
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

通过前面对AQS的学习(可以参考JUC--AQS源码分析(二)同步状态的获取与释放)我们知道,在自旋等待中会去调用tryAcquire来获取锁。针对非公平锁的tryAcquire方法源码如下:

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

可以看出来这里直接调用了父类Sync的nonfairTryAcquire方法。

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;
        }

从上面我们可以看出,其实锁的获取就是在同步状态为0的情况下进行设置,并且设置锁持有的线程为当前线程。当然如果这一系列操作如果失败,就再次循环直到设置状态和设置当前线程为锁持有线程成功(这就是达到线程阻塞的效果)

3.1.2  公平锁获取锁

前面我们看了非公平锁获取锁的整个流程,下面我们来看看公平锁的整个流程有什么不同,并且所谓的公平到底是如何体现出来的。

final void lock() {
            acquire(1);
        }

从上面可以看出,公平锁与非公平锁的不同之处在于这里没有立刻进行获取锁的操作,而是直接调用AQS的acquire方法来实现公平性,那么这个公平性到底是怎么实现的呢?我们还得看FairSync的tryAcquire方法的源码。

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;
        }

通过比较NonfairSync和FairSync的tryAcquire方法,我们看出来后者多了hasQueuedPredecessors方法,就是通过这个方法来保证公平性的。

public final boolean hasQueuedPredecessors() {
       
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;

        //头节点 != 尾节点
        //同步队列第一个节点不为null  
        //当前线程是同步队列第一个节点
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

上面这个方法就保证了先进行等待的线程优先获取到锁的效果。

3.2 lockInterruptibly响应中断获取锁

首先我们来理一下方法调用的顺序。

JUC--ReenTrantLock学习(二)源码分析之获取锁

从上面的方法调用流程图我们可以看出针对响应中断的获取锁方法的响应中断处理其实是在AQS的模板方法中实现的,二获取锁操作和lock方法的获取锁操作是相同的。那么我们这里就来看看响应中断到底是怎么实现的。

下面我们就来看看AQS的acquireInterruptibly方法。

public final void acquireInterruptibly(int arg)
            throws InterruptedException {

        //判断线程是否中断,如果中断则直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

从上面我们可以看出中断处理就是检查线程是否中断然后进行异常抛出。针对进一步doAcquireInterruptibly方法的调用这里就不讲解了,需要了解的可以参考JUC--AQS源码分析(二)同步状态的获取与释放

3.2 tryLock锁申请等待

针对tryLock我们直到可以实现在获取锁的时候如果没有获取到则等待指定时间的功能,这里我们依然来看一下方法调用的顺序。

JUC--ReenTrantLock学习(二)源码分析之获取锁

从上面的方法调用流程我们可以看出,这里的等待的实现主要是依靠AQS来实现的,其余的逻辑和lockInterruptibly相同,具体这里的等待是如何实现的就不详细讲解了,需要了解的可以参考JUC--AQS源码分析(二)同步状态的获取与释放

至此,已经完成了ReentrantLock获取锁的源码的学习,针对释放锁和其余部分功能的源码,我们将在接下来的文章中讲解。