ReentrantLock(重入锁)

本文详细解析了Java中的ReentrantLock,重点介绍了其作为可重入锁的特性,以及公平锁和非公平锁的区别。在加锁过程中,非公平锁允许线程直接尝试获取锁,而公平锁则会考虑线程等待的顺序。锁的释放过程通过调用`tryRelease`确保只有锁的持有者才能释放,并检查是否彻底释放。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重入锁定义

如类名可知,锁的类型是可重入锁,是目前JAVA中使用较为广泛的锁,也就是说当前持有锁的线程再次获取不会被阻塞。ReentrantLock支持两种锁:公平锁和非公平锁。如果锁是公平锁那么获取顺序就应该符合请求上的绝对时间顺序,满足FIFO。

ReentrantLock解决如下两个问题:

1.在线程获取锁的时候;如果已经获取锁的线程是当前线程的话则直接再次获取成功;

2.由于锁会被获取n次;那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。

//可重入锁ReentrantLock,通过其内部实现AbstractQueuedSynchronizer的Sync类来完成锁的功能

public class ReentrantLock implements Lock, java.io.Serializable {

  private final Sync sync;

  //是否生成公平锁实例,默认情况下生成NonfairSync非公平锁.

  public ReentrantLock(boolean fair) {

    sync = fair ? new FairSync() : new NonfairSync();

  }

}

线程加锁(独占)

独占模式加锁分为公平锁与非公平锁,具体加锁的流程分为如下几个步骤:

1,程序通过lock为入口,通过acquire来获取线程锁,这里调用公平锁与非公平锁各自实现中的tryAcquire来实现。

2,当tryAcquire返回true表示抢占锁成功.否则执行如下流程:

3,线程抢占锁失败(addWaiter),先生成一个独占模式的node节点,并把节点添加到队列尾部.

4,通过acquireQueued不断的轮询等待队列,尝试抢占线程锁(其实这里在轮询一次抢占失败后会BLOCKED线程,等待线程的前继节点唤醒后在重新进行抢占).

下面分析下非公平锁与公平锁的实现.

ReentrantLock只实现了Look的独占模式加锁。

非公平锁

ReentrantLock的非公平锁由其内部类”NonFairSync”实现,包含两种情况:

  1. 如果线程来抢占锁资源时,锁没有被其它线程持有,直接设置锁的数量为1,并设置锁的持有线程为当前线程。
  2. 如果锁已经被线程持有,通过AQS.acquire函数尝试线程锁的重入,尝试重入线程锁时非公平锁的实现由Sync.nonfairTryAcquire函数实现。

实现流程如下所示

//ReentrantLock独占模式下非公平锁的加锁流程.

static final class NonfairSync extends Sync {

       //a,ReentrantLock非公平锁的加锁实现

    final void lock() {

        //1,如果是首次加锁,直接设置当前ReentrantLock的所有者为当前线程,并设置锁的数量为1

        if (compareAndSetState(0, 1)) {

            setExclusiveOwnerThread(Thread.currentThread());

        } else {

            //2,这里是线程锁重入,调用AbstractQueuedSynchronizer.acquire来完成加锁.

            acquire(1);

        }

    }

//c,这里由AbstractQueuedSynchronizer.acquire来调用,获取线程锁.

//==>tryAcquire在每次线程被唤醒后都会尝试调用此函数来获取锁资源.

    protected final boolean tryAcquire(int acquires) {

        return nonfairTryAcquire(acquires);

    }

}

非公平锁尝试重入线程锁

Sync.nonfairTryAcquire函数获取锁分为三种场景:

1,如果锁资源没有被任何线程持有,直接设置锁的owner为当前线程,并更新锁的数量加1.

2,锁资源已经被线程持有,判断锁的owner是否为当前线程,如果是重入锁,并更新锁的数量加1.

3,当前线程抢占锁资源失败,返回false,由AQS添加到等待队列等待唤醒,并重复执行此步骤。

//nonfairTryAcquire函数,当前线程尝试获取锁资源.

final boolean nonfairTryAcquire(int acquires) {

    final Thread current = Thread.currentThread();

    int c = getState();

    //tryLock会执行这里判断,如果锁没有被任何线程持有,

    //==>直接更新锁的数量加1,并设置当前线程为锁的owner

    if (c == 0) {

        if (compareAndSetState(0, acquires)) {

            setExclusiveOwnerThread(current);

            return true;

        }

    }

    //这种情况说明当前锁的owner线程就是当前线程本身,直接重入锁,设置锁持有数量加1.

    else if (current == getExclusiveOwnerThread()) {

        int nextc = c + acquires;

        if (nextc < 0) // overflow

            throw new Error("Maximum lock count exceeded");

        setState(nextc);

        return true;

    }

    //流程执行这里,说明线程获取锁失败,由AQS获取锁的流程把当前线程添加到等待队列中.

    return false;

}

公平锁

ReentrantLock的非公平锁由其内部类”FairSync”实现,FairSync.tryAcquire的实现比非公平锁要复杂,每次线程被唤醒来尝试加锁时都会判断等待队列的队顶元素是否是当前线程,只有队顶元素是当前线程或者当前锁的owner是当前线程本身时,才能获取锁资源。

公平锁获取锁资源场景有如下几种:

1,锁已经被线程持有,判断当前线程是否是锁的owner,如果是重入锁资源,并记录锁的数量加1.

2,当前锁没有被任何线程持有,通过AQS.hasQueuedPredecessors判断当前线程是否是等待队列的队顶元素,如果是获取锁资源,并记录锁的数量加1.同时设置锁的owner为当前线程。

3,当前线程抢占锁资源失败,返回false,由AQS添加到等待队列等待唤醒,并重复执行此步骤。

//ReentrantLock独占模式下公平锁的加锁流程.

//公平锁实现与非公平锁实现中区别主要体现在tryAcquire的实现部分.

static final class FairSync extends Sync {

       //线程加锁的入口.

    final void lock() {

        //直接调用AQS(AbstractQueuedSynchronizer)的acquire尝试加锁.

        acquire(1);

    }

//在AQS.acquire中尝试对线程加锁时,公平锁的实现部分代码.

//公平锁主要通过判断队列顶部等待线程是否是当前线程来保证公平性.

    protected final boolean tryAcquire(int acquires) {

        final Thread current = Thread.currentThread();

        int c = getState();

        if (c == 0) {

            //hasQueuedPredecessors返回false的条件:

            //==>1,当前等待队列为空队列,没有线程抢占锁

            //==>2,当前线程就是等待队列中头部节点(当前线程在等待队列的顶部位置).

//====>即:等待队列中当前线程有前继节点返回true.其它情况返回false.

            //如果是上面提到的两种情况,表示线程以加锁,设置锁的owner为当前线程,

            //==>并更新线程的state=1.

            if (!hasQueuedPredecessors() &&

                compareAndSetState(0, acquires)) {

                setExclusiveOwnerThread(current);

                return true;

            }

        }else if (current == getExclusiveOwnerThread()) {

            //当前持有锁的线程就是当前线程本身,直接设置锁的数量加1.

            int nextc = c + acquires;

            if (nextc < 0)

                throw new Error("Maximum lock count exceeded");

            setState(nextc);

            return true;

        }

        //线程抢占锁失败,执行后续与非公平锁相同的流程.

        return false;

    }

}

//AQS.hasQueuedPredecessors,判断队顶元素是否是当前线程,如果不是当前线程返回true.

//==>即:等待队列中当前线程有前继节点返回true.其它情况返回false.

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());

}

锁的释放

由于加锁需要分析公平锁与非公平锁的实现,而锁的释放ReentrantLock.unlook()只是直接调用了“AbstractQueuedSynchronizer”的release函数来进行释放(release调用Sync.tryRelease来具体处理锁的释放),这里我们先分析如何释放锁的流程。

AQS.release函数实现请参考(9.AQS.release的分析部分)

//ReentrantLock释放锁的流程,这里只是简单调用了sync.release.

public void unlock() {

    sync.release(1);

}

//ReentrantLock.Sync中tryRelease的实现。

protected final boolean tryRelease(int releases) {

    //得到当前锁的状态(即当前锁的个数),把线程对应加锁的数量减一.

    int c = getState() - releases;

    //这里判断当前释放锁的线程是否是锁持有者线程,必须是锁的持有者才能释放.

    if (Thread.currentThread() != getExclusiveOwnerThread())

        throw new IllegalMonitorStateException();

//判断线程锁是否释放完成,如果线程锁释放完成,清空线程持有者,表示其它线程可以来抢占锁资源.

//如果state的值不为0,表示线程有重入锁,线程还在继续持有锁资源.

    boolean free = false;

    if (c == 0) {

        //线程锁释放完成.

        free = true;

        setExclusiveOwnerThread(null);

    }

    //更换锁的状态(持有锁的数量)

    setState(c);

    return free;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值