面试中如何答好:ReentrantLock

2023年 10月 11日 42.2k 0

先了解一下

读本篇前,一定要确保已经读过本公众号的AQS讲解。

我们知道实现一把锁要有如下几个逻辑

  • 锁的标识
  • 线程抢锁的逻辑
  • 线程挂起的逻辑
  • 线程存储逻辑
  • 线程释放锁的逻辑
  • 线程唤醒的逻辑

我们在讲解AQS的时候说过AQS基本负责了实现锁的全部逻辑,唯独线程抢锁和线程释放锁的逻辑是交给子类来实现了,而ReentrantLock作为最常用的独占锁,其内部就是包含了AQS的子类实现了线程抢锁和释放锁的逻辑。

我们在使用ReentrantLock的时候一般只会使用如下方法:

ReentrantLock lock=new ReentrantLock();
 lock.lock();
 lock.unlock();
 lock.tryLock();
 Condition condition=lock.newCondition();
 condition.await();
 condition.signal();
 condition.signalAll();

技术架构

如果我们自己来实现一个锁,那么如何设计呢?

根据AQS的逻辑,我们写一个子类sync,这个类一定会调用父类的acquire方法进行上锁,同时重写tryAcquire方法实现自己抢锁逻辑,也一定会调用release方法进行解锁,同时重写tryRelease方法实现释放锁逻辑。

图片图片

那么ReentrantLock是怎么实现的呢?

ReentrantLock的实现的类架构如下,ReentrantLock对外提供作为一把锁应该具备的api,比如lock加锁,unlock解锁等等,而它内部真正的实现是通过静态内部类sync实现,sync是AQS的子类,是真正的锁,因为这把锁需要支持公平和非公平的特性,所以sync又有两个子类FairSync和NonfairSync分别实现公平锁和非公平锁。

图片图片

因为是否公平说的是抢锁的时候是否公平,那两个子类就要在上锁方法acquire的调用和抢锁方法tryAcquire的重写上做文章。

公平锁做了什么文章?

static final class FairSync extends Sync {
  
        final void lock() {
            acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            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)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

公平锁比较简单,直接调用了父级类AQS的acquire方法,因为AQS的锁默认就是公平的排队策略。

重写tryAcquire方法的逻辑为:

  • 判断当前锁是否被占用,即state是否为0
  • 如果当前锁没有被占用,然后会判断等待队列中是否有线程在阻塞等待,如果有,那就终止抢锁,如果没有,就通过cas抢锁,抢到锁返回true,没有抢到锁返回false。
  • 如果当前锁已经被占用,然后判断占用锁的线程是不是自己,如果是,就会将state加1,表示重入,返回true。如果不是自己那就是代表没有抢到锁,返回false。
  • 公平就公平在老老实实排队。

    非公平锁做了什么文章?

    static final class NonfairSync extends Sync {
          
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
    
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
        
        //nonfairTryAcquire代码在父类sync里面
         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;
            }

    非公平锁也很简单,没有直接调用了父级类AQS的acquire方法,而是先通过cas抢锁,它不管等待队列中有没有其他线程在排队,直接抢锁,这就体现了不公平。

    它重写tryAcquire方法的逻辑为:

  • 判断当前锁是否被占用,即state是否为0
  • 如果当前锁没有被占用,就直接通过cas抢锁(不管等待队列中有没有线程在排队),抢到锁返回true,没有抢到锁返回false。
  • 如果当前锁已经被占用,然后判断占用锁的线程是不是自己,如果是,就会将state加1,表示重入,返回true。如果不是自己那就是代表没有抢到锁,返回false。
  • 公平锁和非公平分别重写了tryAcquire方法,来满足公平和非公平的特性。那么tryAcquire方法也是需要子类重写的,因为它和是否公平无关,因此tryAcquire方法被抽象到sync类中重写。

    sync类中
    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;
            }

    释放锁的逻辑如下:

  • 获取state的值,然后减1
  • 如果state为0,代表锁已经释放,清空aqs中的持有锁的线程字段的值
  • 如果state不为0,说明当前线程重入了,还需要再次释放锁
  • 将state写回
  • 释放锁往往和抢锁逻辑是对应的,每个子类抢锁逻辑不同的话,释放锁的逻辑也会对应不同。

    具体实现

    接下来我们通过ReentrantLock的使用看下它的源码实现

    class X {
                private final ReentrantLock lock = new ReentrantLock();
                Condition condition1=lock.newCondition();
                Condition condition2=lock.newCondition();
                public void m() {
                    lock.lock();
                    try {
    
                        if(条件1){
                            condition1.await();
                        }
                        if(条件2){
                            condition2.await();
                        }
                    } catch (InterruptedException e) {
    
                    } finally {
                        condition1.signal();
                        condition2.signal();
                        lock.unlock();
                    }
                }
            }

    先看这个方法:lock.lock()

    ReentrantLock类
    public void lock() {
            sync.lock();
        }
    NonfairSync 类中
      final void lock() {
        if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
        else
          acquire(1);
      }
    FairSync 类中
      final void lock() {
          acquire(1);
      }

    公平锁和非公平锁中都实现了lock方法,公平锁直接调用AQS的acquire,而非公平锁先抢锁,抢不到锁再调用AQS的acquire方法进行上锁

    进入acquire方法后的逻辑我们就都知道了。

    再看这个方法lock.unlock()

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

    unlock方法内直接调用了AQS的Release方法进行解锁的逻辑,进入release方法后逻辑我们都已经知道了,这里不再往下跟。

    最后看这个方法lock.tryLock()

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

    tryLock方法直接调用sync的tryAcquireNanos方法,看过AQS的应该知道tryAcquireNanos这个方法是父类AQS的方法,这个方法和AQS中的四个核心方法中的Acquire方法一样都是上锁的方法,无非是上锁的那几个步骤,调用tryAcquire方法尝试抢锁,抢不到锁就会进入doAcquireNanos方法。

    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
                throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            return tryAcquire(arg) ||
                doAcquireNanos(arg, nanosTimeout);
        }

    doAcquireNanos这个方法做的其实就是入队,阻塞等一系列上锁操作,逻辑和Acquire方法中差不多,但是有两点不同:

  • 该方法支持阻塞指定时长。
  • 该方法支持中断抛异常。
  • 看下下面的代码

    private boolean doAcquireNanos(int arg, long nanosTimeout)
    throws InterruptedException {
    if (nanosTimeout

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论