美文网首页
ReentrantLock源码分析

ReentrantLock源码分析

作者: 绝色天龙 | 来源:发表于2018-11-01 01:55 被阅读0次

    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源码分析

          本文链接:https://www.haomeiwen.com/subject/duqtxqtx.html