JAVA concurrency — ReentrantLock 源码详解

概述

在jdk中内置了可重入 synchronized
来维护代码同步,但是 synchronized
是在虚拟机层面实现,并且功能比较少,难以满足一些特殊的情况,所以在 concurrency
包中就有了类似的方法 ReentrantLock
。今天我们就一起来分析下 ReentrantLock
源码实现。

基本结构

实际上我们仔细阅读源码后发现 ReentrantLock
的实现很简单。他实现了接口 Lock
,但是他的同步实现实际上是在内部声明了一个叫 Sync
的类来实现的,而 Sync
是继承自同步器的核心类 AbstractQueuedSynchronizer

Sync

实际上 ReentrantLock
的核心全部在 Sync
之上,所以我们重点来研究它。

Sync
是一个声明在 ReentrantLock
内部的抽象类,他继承了 AQS
,然后内部有两个类继承了 Sync
,一个是公平锁 FairSync
,一个是非公平锁 NonfairSync
。这两个类对应了两个锁的不同实现。

Sync实现

Sync的内部方法如下

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();

        final boolean nonfairTryAcquire(int acquires) {}

        protected final boolean tryRelease(int releases) {}

        protected final boolean isHeldExclusively() {}

        final ConditionObject newCondition() {}

        final Thread getOwner() {}

        final int getHoldCount() {}

        final boolean isLocked() {}

        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {}
    }

上述代码我们可以看到Sync内部除了实现了AQS的一些基本的方法之外有两个非常重要的方法,一个是 tryRelease
,另一个是 nonfairTryAcquire

  1. tryRelease

    protected final boolean tryRelease(int releases) {
            // 获取当前state的值,并获取减去releases的数值之后的值
            int c = getState() - releases;
            // 如果当前线程不是独占模式的所有者,那就抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                // 如果c值为0,那就说明锁已经可以释放
                free = true;
                // 设置独占模式的所有者为空
                setExclusiveOwnerThread(null);
            }
            // 将c的修改值存回去
            setState(c);
            return free;
        }

    代码不难懂,主要是这个 releases
    state
    的值代表的意思是什么。 state
    是一个代表锁状态的值,如果为0,则说明锁是空闲可以被获取的,如果大于0则说明锁正在被占用。 ReentrantLock
    是可重入锁就体现在此,每一次获取锁,state的值便会加1,释放的时候也是可以按照需求批量释放,直到state的值变成0,则说明锁完全释放。

  2. nonfairTryAcquire

    final boolean nonfairTryAcquire(int acquires) {
            // 获取当前线程
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果state为0,即当前的锁是空闲状态
            if (c == 0) {
                // 尝试获取锁
                if (compareAndSetState(0, acquires)) {
                    // 如果获取成功则设置独占线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果锁不空闲,但是当前线程就是独占的线程,那么依然可以获取这个锁
            else if (current == getExclusiveOwnerThread()) {
                // 将state加上acquires的值
                int nextc = c + acquires;
                // 如果nextc小于0,说明此时锁的计数溢出了,抛出异常
                if (nextc < 0) 
                    throw new Error("Maximum lock count exceeded");
                // 将c的修改值存回去
                setState(nextc);
                return true;
            }
            return false;
        }

    依然是清晰明了的代码,我们可以很清楚的看到当发现当前线程是持有锁的线程时我们可以反复多次的获取锁,只需要将 state
    加上相应的值便可。这个方法是非公平获取锁,非公平体现在第一次判断上,如果是公平锁的话讲究先来后到,如果发现锁不空闲,应该放到队列中等待,但是他在发现锁不为空闲的时候,首先尝试了一次获取锁(尝试插队),发现获取失败才会老老实实在队列中等待。

NonfairSync实现

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        // ReentrantLock核心方法加锁
        final void lock() {
            // 非公平的方式,首先尝试CAS改变state的值,成功则获取锁,失败在尝试公平获取
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        // 调用父类中的非公平锁实现
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

FairSync实现

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        // 公平的获取锁的实现
        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;
        }
    }

总结

ReentrantLock
的实现非常的简单,因为同步器的核心实现是在 AQS
中的,所以在 ReentrantLock
中我们能够涉及到的无非就是可重入锁的实现以及公平锁非公平锁的区分。

原文 

https://segmentfault.com/a/1190000022293588

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » JAVA concurrency — ReentrantLock 源码详解

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址