08.JUC 锁 - ReentrantLock

基本概念

ReentrantLock,即可重入锁。可重入锁指的是在一个线程中可以多次获取同一把锁,比如:一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁。

内部构造

首先来看它的继承关系:

《08.JUC 锁 - ReentrantLock》

在 ReentrantLock 有三个重要的内部类:

// 同步器
static abstract class Sync extends AbstractQueuedSynchronizer {...}

// 非公平同步策略
final static class NonfairSync extends Sync {...}

// 公平同步策略
final static class FairSync extends Sync {...}

如代码所示,NonfairSync 、FairSync 都继承自 Sync 。它们负责 ReentrantLock 的同步策略。在构建 ReentrantLock 时需要指定同步策略。

  • 采用公平同步策略构建称为公平重入锁锁。
  • 采用非公平同步策略构建的称为非公平重入锁。

ReentrantLock 默认会构建一个非公平的重入锁,只有当入参为 true,才会构建一个公平的可重入锁。

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = ( fair ) ? new FairSync() : new NonfairSync();
}

再来看看它继承自 Lock 接口的操作方法:

// 获取锁
public void lock() {
    sync.lock();
}

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException{
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

 // 解锁
public void unlock() {
    sync.release(1);
}

lock

在 ReentrantLock 中 lock 操作表示获取锁,该过程的实现交给了 Sync 类,而在 ReentrantLock 中还定义了 Sync 的两个子类:FairSync、NonFairSync。因此获取锁的操作可分为:获取公平锁、获取不公平锁

在 FairSync 类的 lock 实现如下:

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

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

在 AQS 中并不对 tryAcquire 方法实现,而是留给了子类来展开。该方法表示获取锁,获取锁失败,则执行 acquireQueued 的内容。

tryAcquire 方法在 FairSync 、NonFairSync 类中作了不同的实现。

1.获取公平重入锁

首先来看 FairSync 类中 tryAcquire 方法:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();

    // 获取锁的重入计数,为 0 说明没有线程拥有锁
    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){
            // 抛出异常...
        }   
        setState(nextc);
        return true;
    }
    return false;
}

// 在 AQS 类中定义
public final boolean hasQueuedPredecessors() {
    Node t = tail; 
    Node h = head;
    Node s;

    // 当 ①[等待队列]不为空 ②队列中首节点(头节点的下一个节点)的线程不是当前线程 返回 true
    return h != t && 
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

如下图所示,当等待队列是以下情况时,hasQueuedPredecessors 返回 false,表示在等待队列中没有其他线程在等待(公平锁的公平在此体现):

  • 等待队列为空
  • 包含该线程的节点在等待队列的首节点

《08.JUC 锁 - ReentrantLock》

通过上面的分析,可以知道当前线程在获取公平锁时,若等待队列中有其他线程在等待,则它就会被加入等待队列的末尾,进入线程等待状态。

之所以叫公平锁,就是等待队列中体现了一个 FIFO(先进先出) 的顺序。

2.获取非公平重入锁

在 NonFairSync 类的 lock 实现如下:

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

同 FairSync 大致一样,它的调用过程如下 NonFairSync.tryAcquire -> Sync.nonfairTryAcquire。代码如下:

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

// Sync
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 关键 -> 少了 hasQueuedPredecessors 的判断
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0){
            // 抛出异常...
        }   
        setState(nextc);
        return true;
    }
    return false;
}

观察代码,获取非公平锁的操作与获取公平锁相比,差别在 tryAcquire 方法,进一步讲是差在 hasQueuedPredecessors 的判断上。

lockInterruptibly

该方法表示获取锁,但不忽略中断。

public void lockInterruptibly() throws InterruptedException {
    // 调用 AQS 类的 acquireInterruptibly
    sync.acquireInterruptibly(1);
}

tryLock

tryLock 表示尝试获取锁,成功返回 true,不成功则返回 false。

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

tryLock(long timeout, TimeUnit unit) 表示尝试获取锁,成功返回 true,尝试获取失败,则线程进入阻塞状态一段时间。

public boolean tryLock(long timeout, TimeUnit unit) 
    throws InterruptedException {
    // 调用 AQS 类的 tryAcquireNanos
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

unLock

表示释放锁操作,该从操作可分为两步骤:

  • 修改锁的重入计数。
  • 若锁的重入计数为 0 表示当前线程不再持有锁,则唤醒同步等待队列的中的节点。

实现过程如下:

// ReentrantLock.unlock
public void unlock() {
    sync.release(1);
}

// AQS.release
public final boolean release(int arg) {
    // ①尝试释放锁
    if (tryRelease(arg)) {
        Node h = head;
        // 判断等待队列不为空
        if (h != null && h.waitStatus != 0){
            // ②唤醒等待队列中头节点的后继节点
            unparkSuccessor(h);
        }
        return true;
    }
    return false;
}

在介绍 AQS 独占模式时说过,tryRelease 交给 AQS 的子类来实现。

// ReentrantLock.Sync
protected final boolean tryRelease(int releases) {
    // 执行释放操作后,锁的重入计数
    int c = getState() - releases;

    // 因为是独占锁,释放锁的前提是线程持有锁
    if (Thread.currentThread() != getExclusiveOwnerThread()){
        // 抛出异常...
    }

    // 为 0 表示没有线程持有锁
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

newCondition

返回一个条件队列。

  • 实现过程:
public Condition newCondition() {
    return sync.newCondition();
}

final ConditionObject newCondition() {
    return new ConditionObject();
}
  • 调用过程:
private ReentrantLock lock = new ReentrantLock();
private Condition con= lock.newCondition();
    原文作者:JUC
    原文地址: https://blog.csdn.net/u012420654/article/details/56496973
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞