可重入锁 - 源码跟踪ReentrantLock

可重入锁 - ReentrantLock

概念

可重入锁,是指当前线程可以对某个资源重复加锁也不会导致死锁发生的锁。

注意:在使用可重入锁的时候,需要注意,线程对资源a加了多少次锁,就要释放多少次,如果加了5层锁而释放了4层,则资源不会被释放。

JAVA中的ReentrantLock

家族树

可重入锁的家族树非常简单,实现了Lock接口以及可序列化,可以说可重入锁的几个主要方法,都是由Lock要求的,下面我们就细看一下ReentrantLock的几个主要方法。

ReentrantLock的主要方法

  1. 构造器:ReentrantLock() & ReentrantLock(boolean fair)
    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可重入锁的构造器主要干了一件事,初始化这把锁持有的同步类,默认是持有一把非公平锁。

公平锁和非公平锁的概念,不在这篇里详细地阐述了,但可以用一句话解释一下,即,等待队列中所有线程获取锁的机会均等,那么这把锁就是公平锁,否则,如果是有一定顺序的让线程获取资源,那这个资源上的锁就是非公平锁。

  1. lock()
/**
     * Acquires the lock.
     *
     * <p>Acquires the lock if it is not held by another thread and returns
     * immediately, setting the lock hold count to one.
     *
     * <p>If the current thread already holds the lock then the hold
     * count is incremented by one and the method returns immediately.
     *
     * <p>If the lock is held by another thread then the
     * current thread becomes disabled for thread scheduling
     * purposes and lies dormant until the lock has been acquired,
     * at which time the lock hold count is set to one.
     */
    public void lock() {
        sync.lock();
    }

跟入到ReentrantLock的锁方法,可以看到,ReentrantLock使用的确是一个类似于门面的方式,底层调用的都是内部实现的同步类。

在注释中,我们可以看到,可重入锁声明了锁资源的三种情况:

  • 如果资源未被另一个线程持有,则马上返回,并让锁统计变为1。
  • 如果线程已经持有了当前资源,则锁统计+1,并且马上返回。
  • 如果线程已经被另一个线程持有了,那么当前线程就会进入休眠状态,直到锁被获取。
  1. lockInterruptibly()

获取锁,逻辑是和lock()一样的,但是能响应中断。

  1. trylock() & tryLock(long timeout, TimeUnit unit)

尝试获取锁,获取逻辑和lock()差不多,但是并不会等待锁

  1. unlock()

解锁,使加在资源上的锁统计-1,直到锁统计为0,资源才被真正释放。

  1. newCondition()

返回一个锁的实例,可以用于多线程之间的通信,功能比synchronize还要强大。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Pzx7dIF-1597632367029)(./image-20200817005129206.png)]

ReentrantLock中锁方法的细节实现

非公平锁

此处的非公平锁是ReentrantLock的内部抽象类Sync的内部实现类NonfairSync,Sync类继承了AbstractQueuedSynchronizer类

				/**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

首先,非公平锁会先尝试使用unsafe类的CAS方法获取锁,失败则执行acquire(1这个方法)。值得注意的是,这里要求原值为0,也就是资源无锁,这个方法才有可能执行成功,但凡有一把锁,不管是不是本线程持有的,都会走进acquire(1)这个方法。

		/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这个方法是AbstractQueuedSynchronizer类中实现的方法,看似只有一个逻辑分支,其实有三步逻辑:

  1. tryAcquire(arg),获取锁,这里arg=1。
  2. 如果这步没有成功,才会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),将线程放入请求等待队列。
  3. 如果没获取到锁,还成功加入了请求队列,那就等着呗,于是有了第三步,selfInterrupt()-自停。

而tryAcquire(arg)底层调用的是Sync#nonfairTryAcquire(int acquires)

/**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            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;
        }

在这段代码里面,我们还看到了nextc这个变量会做一个正值判断,没错,对一个线程来说,加在一个资源上的锁最多只能有32767把,思考的还挺周全的(笑)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值