ReentrantLock源码分析

ReentrantLock源码分析

前言

最近公司比较忙,整天忙着做项目、做需求,感觉整个人昏昏沉沉的,抬头看天空感觉都是灰色的~~,其实是杭州的天本来就是这个颜色,手动滑稽~\(^o^)/~。废话不多说,今天突然回忆起面试的时候问到的锁,继而就想起了ReentrantLock这个类,我们知道,JDK1.6已经对synchronized做了很多的优化,性能上已经不比ReentrantLock差了,甚至在竞争激烈的情况下性能还要好很多,那为什么还要研究ReentrantLock呢?首先当然是ReentrantLock有synchronized不能实现的功能,其次就是研究一下ReentrantLock的实现思想,毕竟里面用到了Java并发中高频的AQS。ReentrantLock能做到synchronized做不到的这里不做深究,概括来说就是ReentrantLock可以公平锁,细分条件,可中断。

ReentrantLock中有两个类,FairSync和NonfairSync,分别用于实现公平锁和非公平锁,这里主要研究一下它们的加锁过程,先来看公平锁FairSync

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

纳尼?就一行代码?当然没有这么简单,不管是FairSync还是NonfairSync都是继承自ReentrantLock里面的一个类Sync,而Sync又是一个AQS的实现(这里不对AQS作展开,想要了解的同学可以自行google或者baidu)。看一下Sync的acquire方法,并没有重写,用的父类方法

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

其实AQS的实现类只要实现tryAcquire方法就可以了,这样调用acquire方法就能够有不同的表现。先从方法的表面意思来猜测一下,tryAcquire(arg)就是尝试获得锁,false应该是获得失败,所以if内语句执行的第一个条件就是获得锁失败;acquireQueued(addWaiter(Node.EXCLUSIVE), arg))这个应该是加入等待的队列,Node.EXCLUSIVE翻译过来是独占,那就是独占模式加队列?先这么理解吧,那如果条件成立会做什么呢?

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

其实就是当前线程尝试中断(置中断标志位),那总结一下:首先当前线程尝试获得锁,如果失败就尝试加入等待的队列(以独占模式,先不管这事啥意思),这里补充一下,acquireQueued中如果线程被中断是会返回true的,所以线程不中断就会停在acquireQueued方法,里面也会再次调用tryAcquire方法的。好了,猜测终归是猜测(其实也是看了实现的),先看看tryAcquire方法,FairSync中的

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // c记录的是当前同步器的状态,state是AQS的变量,是个volatile变量
    int c = getState();
    // 等于0代表没有其他线程获得锁
    if (c == 0) {
        // hasQueuedPredecessors()会判断当前等待的队列中是否有排在前面的线程,因为是公平锁,所以要先到先得
        // 这里用CAS的原因是hasQueuedPredecessors()方法判断的只是当时的情况,返回后可能有其他线程已经抢占锁了
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 锁重入
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        // 不能超过Integer的最大值
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 其他情况获取锁失败
    return false;
}

代码中我都加了注释,相对来说tryAcquire方法还是比较好理解的,再来看看addWaiter(Node.EXCLUSIVE)方法

private Node addWaiter(Node mode) {
    // 构造当前线程的Node,Node是等待队列的基本单位
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        // 将当前node接在原来尾部node的后面,CAS成功则建立双向链表,然后返回当前node
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 不停尝试将当前node加到队列的尾部
    enq(node);
    return node;
}
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

不管怎样,addWaiter(Node.EXCLUSIVE)方法返回的都是代表当前线程的node,然后又作为入参调用了acquireQueued()方法,来看看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法做了什么

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 如果当前node紧跟在head之后,则再尝试获取锁,head从前面的过程来看其实是一个没有意义的new Node(),获取成功当然就返回了
            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);
    }
}

总结:首先尝试获得锁,获得失败就不停地把自己加到队列末尾,然后就不停的判断是否轮到自己来获得锁了,如果获得成功就会把自己从队列中移除,这期间如果出现线程中断则取消获取锁的操作。

因为FairSync和NonfairSync的unlock()方法都是相同的继承自父类,所以最后统一分析,先讲NonfairSync的lock()方法,这里多了一个if的分支,为什么这样就是不公平了呢?因为这里第一步执行的就是:如果没有其他线程获得锁那就我来吧,它才不管你是不是有线程在队列中排队。

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

非公平锁的tryAcquire()方法和公平锁是有区别的,实现了非公平的策略

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

乍看nonfairTryAcquire()方法,明明和公平锁的tryAcquire()方法一样嘛,搞飞机呢,重写了一遍。但其实这里还是稍微有点不同的,就是在没有线程获得锁的情况下,非公平策略是不会去判断是否队列中有其他node在自己前面的,直接就是一个CAS,成则成已,不成我也认了,排队这种事,不存在的,嘿嘿。

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

最后看一下两种锁的释放,unlock()方法

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

又是简单粗暴。。。继续看Sync的release()方法

public final boolean release(int arg) {
    // 尝试释放锁成功,唤醒本node的接班node,尝试失败返回false,但是外层并没有用到返回值
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

锁几次就要释放几次

protected final boolean tryRelease(int releases) {
    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);
    // 未全部释放返回false则外层不会去唤醒接班的node
    return free;
}

ReentrantLock关于锁的获得和释放差不多就是这个样子,其实还是有一些细节没有很深入的讲的,主要是对node等待队列的一些操作,包括阻塞、唤醒接班人等,但这就涉及到JDK这么设计的思想了,感觉有点偏离了锁实现这个概念,理解ReentrantLock大可以跳过这部分。而且我主要讲的是独占模式的锁,等有空把共享模式的也补充一下。

你可能感兴趣的:(ReentrantLock源码分析)