ReentrantLock原理及源码阅读

ReentrantLock原理及源码阅读

1、ReentrantLock介绍

ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取到该锁,其他线程获取该锁的线程将会被阻塞而被放入该锁的AQS阻塞队列里面。

ReentrantLock最终还是使用AQS来实现的,并且根据参数来决定其内部是一个公平还是非公平锁。

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * Performs {@link Lock#lock}. The main reason for subclassing
     * is to allow fast path for nonfair version.
     */
    abstract void lock();
    ....
}

其中Sync类直接继承自AQS,它的子类NonfairSyncFairSync分别实现了获取锁的公平和非公平策略

2、获取锁的方法

void lock()方法

当一个线程调用该方法时,说明该线程希望获取该锁,如果锁当前没有被其他线程占用,并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程,并设置AQS的状态值为1,然后直接返回。如果当前线程之前已经获取过该锁,则这次只是简单的把AQS的状态值加1后返回。如果该锁寂静被其他线程持有,则调用该方法的线程会被放入AQS队列后阻塞挂起。

public void lock() {
    sync.lock();
}

ReentranLock的lock委托给了sync类,根据创建ReentranLock的构造函数选择sync的实现是非公平还是公平,这个锁是一个非公平锁或公平锁。以下是sync的子类NonfairSync的情况,也就是非公平锁。

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

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        // CAS 设置状态值state
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 获取失败则调用AQS的acquire方法
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

上述代码中,默认AQS状态值state为0,所以此一个调用lock的线程会通过CAS设置状态值为1,CAS成功则代表当前线程获取到了锁,然后通过setExclusiveOwnerThread设置该锁的持有者为当前线程。

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

如果有其他线程调用lock方法获取该锁,CAS会失败,然后调用AQS的acquire方法,传递的参数为1.

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

AQS并没有实现tryAcquire方法,tryAcquire方法需要子类自己定制化,所以上面的tryAcquire实际上会调用ReentrantLock重写的tryAcquire方法。

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 获取当前状态值。
    int c = getState();
    // 当前状态值为0则通过CAS获得锁
    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;
    }
    //  为拿到锁返回false,然后该线程被放入AQS阻塞队列。
    return false;
}

非公平锁的体现:假设线程1调用lock方法时发现state状态值不为0,且当前线程不是锁的持有者则返回false,然后当前线程被放入AQS阻塞队列。这时线程2也调用了lock方法,发现当前状态值为0了(假设占有该锁的其他线程释放了该锁),所以通过CAS设置获取到了该锁。明明是线程1先请求获取的该锁,然而线程2获取到了锁,这就是非公平的体现。这里线程2获取锁前并没有查看当前AQS队列里面是否有比自己更早请求该锁的线程,而是使用了抢夺策略。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 当前state为0.
    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;
}

上述代码为公平锁的方法,公平锁的话只需要看FairSync重写的tryAcquire方法。

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

上面代码中,是实现公平性的核心代码,如果当前AQS队列为空或者当前线程是AQS的第一个节点则返回false。其中如果ht则说明当前队列为空,直接返回false。如果h!=t且snull则说明有一个元素将要作为AQS的第一个节点入队列,那么返回true,如果h!=t并且s!=null和s.thread !=Thread.currentThread则说明队列里面的第一个元素不是当前线程,那么返回true。

3、释放锁的方法

void unlock()方法
public void unlock() {
    sync.release(1);
}

尝试释放锁,如果当前线程持有该锁,则调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后当前状态值为0,则当前线程会释放锁,否则仅仅只是减1而已。如果当前线程没有持有该锁而调用了该方法则会抛出异常。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 如果不是锁的持有者,则抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果当前可重入次数为0, 则清空锁持有线程
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 设置可重入次数为原始值-1
    setState(c);
    return free;
}
posted @ 2022-05-30 14:06  YoungerWb  阅读(22)  评论(0编辑  收藏  举报