并发和多线程(十六)--ReentrantLock源码解析


前面刚学习了AQS的基本原理,主要通过两个队列实现功能(同步队列+等待队列,前者是双向链表,实现加锁和解锁,后者是单向链表,用做同步协作,阻塞、唤醒),正好可以趁热打铁,了解一下ReentrantLock的源码,有了AQS的基础,阅读ReentrantLock的源码是非常简单的,如果没有了解AQS原理的同学,可以参考:
AbstractQueuedSynchronizer源码(上)--排他锁
AbstractQueuedSynchronizer源码(下)--共享锁和Condition条件队列
我们都知道ReentrantLock的底层就是通过AQS实现,和Synchronized一样都是可重入锁,同时也是排他锁,所以ReentrantLock只能一个线程获取锁,但是线程可以获取当前资源的多重锁,对应锁数量+1,每次释放-1,直到等于0,才可以被其他线程竞争获取锁,有点引用计数法的意思。

类定义:

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
    //默认构造函数,得到的事非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    //支持传入fair参数,得到是否公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
}

ReentrantLock实现Lock接口,序列化,这里并没有实现AQS接口,而是通过内部类Sync实现,然后Sync作为成员变量,实现各种方法,Sync我们会在下面说。先来了解一个Lock接口。

Lock:

public interface Lock {

    //加锁,如果获取不到锁会到同步队列中阻塞
    void lock();
    //获取可中断锁
    void lockInterruptibly() throws InterruptedException;
    //尝试获取锁,成功返回true,否则返回false
    boolean tryLock();
    //带有超时时间tryLock
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    //解锁
    void unlock();
    //得到condition实例
    Condition newCondition();
}

这些方法在ReentrantLock中实现,主要通过静态内部类Sync实现,也是Sync实现了AQS。。

Sync:

Sync并没有实现加锁,而是通过两个子类FairSync和NonfairSync实现,FairSync和NonfairSync对应着公平锁和非公平锁实现方式。

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

    //加锁,抽象方法,子类实现
    abstract void lock();

    //尝试获取费公平锁,有两个地方调用,可自行通过Ctrl+Alt+H查看
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //如果当前没有现成持有锁
        if (c == 0) {
            //CAS将状态从0设置为参数值
            if (compareAndSetState(0, acquires)) {
                //将当前线程设置到AbstractOwnableSynchronizer中
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //如果当前锁被持有,当前线程为持有锁线程
        else if (current == getExclusiveOwnerThread()) {
            //ReentrantLock为可重入锁,可以对资源多次加锁
            int nextc = c + acquires;
            //超过integer的最大值,抛出异常
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            //直接设置state,返回true
            setState(nextc);
            return true;
        }
        //如果持有锁的线程不是当前线程,返回false,获取锁失败
        return false;
    }
    
    //尝试释放锁
    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():

上面看了tryRelease代码,内容比较简单,state为0表示表示release成功,如果线程对资源加锁多次,那么解锁也需要相应的次数,才能release,然后被其他线程竞争这把锁。
PS:释放锁tryRelease是不区分共享锁和排他锁的。

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) {
            //判断当前线程是否为同步队列head节点的后驱节点的线程,如果是可以获取锁,false阻塞等待,这是实现公平锁的关键,实现线程在队列的先入先出
            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;
    }
}

FairSync

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    //非公平锁加锁
    final void lock() {
        //CAS尝试将state从0设置为1,成功,设置持有锁线程,否则acquire获取锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    //这里调用了前面Sync定义的nonfairTryAcquire()
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

总结:
对于加锁,解锁的流程,调用lock()unlock()的过程可以自己看下api,算是比较简单,也就不写代码了。ReentrantLock直接使用AQS整体实现非常简单,所以掌握AQS还是很有必要的,包括后面了解CountDownLatch、Semaphore实现都是很有必要的。

posted @ 2022-01-22 14:06  Diamond-Shine  阅读(30)  评论(0编辑  收藏  举报