ReentrantLock原理

113 阅读3分钟

昨天讲完了AQS,今天来看看实现吧。先准备介绍ReentrantLock,剩下的之后再介绍。

有了之前AQS的基础,再来看这些实现,真的是简单的不行,主要是我们来学习这种很牛的思想,对我们的编码水平是一个很大幅度的跨越。好了,话不多说,速来讲解,哈哈哈。

整体结构预览

打开ReentrantLock的源码,直接就发现了一个Sync类继承了我们之前的讲解的AQS,在这里他实现了我们AQS独占锁的非公平获取(nonfairTryAcquire)和释放资源state(release)的逻辑。

但是我们发现Sync还是一个抽象类,具体的是被FairSync和NonfairSync给继承了。那么具体的两个实现类是干啥的呢,ReentrantLock他是支持公平锁和非公平锁的,所以他的架构是这样。那么什么是公平锁和非公平锁呢,如果线程访问顺序和资源获取顺序是一致的,那他就是公平的,如果不一致那就是非公平的,比如现在A,B入队了,现在来了一个C,他直接插队到AB前面去了,那就是非公平的了。

NonfairSync中,我们见到了久违的tryAcquire获取资源,以及一个Sync类的抽象方法lock。

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

FairSync中,我们也看到了tryAcquire,以及lock。

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

你把他俩的lock方法一对比,就看出了确实他们是非公平和公平的了。NonfairSync中的lock,他多了一个if,如果现在队列中已经有AB了,那么假如A现在获取了资源,然后释放,state为0,再即将唤醒B的时候,C来了,他直接执行if成立,插队成功,B大喊这不公平!!!

构造方法

而在构造方法中,我们可以看到他默认的是非公平锁。

图片.png

而为啥默认是非公平锁呢?在多数场景中,非公平锁的并发度确实比公平锁的高。

图片.png 注:图片来自《Java并发编程的艺术》

NonFairSync

获取锁

ReentrentLock.lock()->Sync.lock()->NonFairSync.lock()->tryAcquire()->nonfairTryAcquire()

final void lock() {
    if (compareAndSetState(0, 1))//不公平
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);//顶层AQS的逻辑
}
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);//适配器
}
final boolean nonfairTryAcquire(int acquires) {//非公平的tryAcquire
    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;
}

加入来了三个线程ABC,假设A抢到了锁,BC线程再执行lock里面的if就不行了,走的else,然后尝试获取获取资源,发现不得行,返回了false。然后就被顶层的队列管理大哥AQS给入队了。嘶,AQS你也太棒了吧!

释放锁

ReentrantLock.unlock()->AQS.release()->AQS.tryRelease()->Sync.tryRelease()

public void unlock() {
    sync.release(1);
}
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;
}

哈哈哈,看完了AQS之后,确实挺简单的。别人问你RenntrantLock怎么实现非公平的啊?他让插队!

FairSync

而公平锁,就是在lock中没有添加if的条件,不让插队。其他的内容相差不大

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

总结

公平锁和非公平锁,其他的大部分内容还是复用AQS的。