ReentrantLock 实现原理详解

上个月入职新公司,忙的一直没时间抽出时间来写文章,有点惭愧,今天晚上感觉挤出一些时间来写一篇文章。

显式锁的实现其实是基于AQS同步器,所以阅读本篇文章之前,建议先理解AQS同步器的原理。可以看我之前写的一篇文章:
Java同步器框架-AQS原理&源码解析

Sync

ReentrantLock内部定义了一个抽象类Sync,实现了具体的tryRelease()方法,但没有tryAcquire()方法的实现。后面又定义了Sync的子类FairSync和NonfairSync两个类,分别代表两种策略,一种是公平的方式去获取锁,一种以非公平的方式获取锁。

我们先看一下公共的tryRelease()方法实现

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

tryRelease()方法很简单,主要判断释放资源后state是否为0,如果为0就返回true,接下来就是AQS的内部工作了,会唤醒正在阻塞的线程。

FairSync和NonfairSync的不同主要体现在tryAcquire()方法,我们来看下他们各自的实现

FairSync

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;
                }
            }
            //如果当前线程和占用锁的线程是同一个线程,就可以继续进入
            //ReentrantLock的可重用性就是通过这个来体现的
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
}

从上面的代码可以看出,FairSync会很优雅的去看一下前面有没有人在排队,没有人排队的话才去尝试获取锁(不一定能获取成功,因为可能同时有其他人也才尝试获取锁,失败的人就会进入队列排队)。

NonfairSync

final void lock() {
            //先尝试直接设置state状态,设置成功了说明锁获得成功,其他线程看到state!=0就会进入休眠
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
}

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}
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;
}

上面的代码可以看出,无论是lock方法还是nonfairTryAcquire方法,NonfairSync都是先尝试设置state状态为1,只要通过cas设置成功了,就说明成功获取到锁了。我们可以看nonfairTryAcquire这个方法,NonfairSync和FairSync最大的区别就是没有!hasQueuedPredecessors()这一步判断,也就是在这个策略里面,锁申请者会先直接尝试获取锁,由于占用锁的线程释放锁到通知CLH队列的第一个排队者来获取是有个时间差的,非公平模式的锁申请者可能在这个时间差里强行插队获取到锁,这就造成了不公平的现象,违反了先到先得的规则。

ReentrantLock的一些方法实现

理解了NonfairSync和FairSync,再来看ReentrantLock里面一些常用的方法。

public void lock() {
        sync.lock();
}
public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
}
public void unlock() {
        sync.release(1);
}

我们可以看到常用的lock()方法,tryLock()方法,unlock()方法,实现都很简单,都是直接调用sync的方法。sync的实现策略决定了lock的获取锁的策略不同。

比较有意思的是,tryLock()调用的始终是不公平的nonfairTryAcquire()方法,与sync的具体实现策略无关。
nonfairTryAcquire()方法中,只要能把state设置成1或者可重入就说明获取到锁了,然后结果也会返回true,否则就返回false。和tryLock()的语义类似。

其他的方法也基本都是基于Sync和AQS一些原有的实现,理解了AQS后再来看这些方法就一目了然了,这里就不细讲了。

Condition

ReentrantLock 里面有个newCondition()方法,也是调用sync的方法

public Condition newCondition() {
        return sync.newCondition();
}
//ReentrantLock.Sync
final ConditionObject newCondition() {
      return new ConditionObject();
}

ConditionObject是AQS的一个内部类,它实现了Condition类,所以相应的实现了await(),signal()等方法。
当对某个线程执行ConditionObject的await()方法进入休眠的时候,会释放该线程所持有的AQS的资源,从而让其他的线程可以得到资源。
同理,当执行signal()唤醒某个线程时,会先尝试获取锁,获取失败的话也和正常的线程一样进入AQS的CLH队列中等到。

CyclicBarrier类中就用到了ReentrantLock的newCondition()方法。

总结

ReentrantLock也是基于AQS实现的,所以理解了AQS后再来看ReentrantLock的实现就很简单了。再理清楚公平锁和非公平锁的实现策略,就可以很清晰的了解它的整个原理了。

如果哪里有写的不对的地方,烦请指出,感激不尽!

猜你喜欢

转载自blog.csdn.net/u013332124/article/details/80248859