AQS与ReentrantLock原理

本文详细解析了AQS同步框架,特别是如何基于AQS实现自定义阻塞式锁,并介绍了ReentrantLock的原理,包括锁的获取、释放以及可重入特性。

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

推荐你阅读
互联网大厂万字专题总结
Redis总结
JUC总结
操作系统总结
JVM总结
Mysql总结
互联网大厂常考知识点
什么是系统调用
CPU底层锁指令有哪些
AQS与ReentrantLock原理
旁路策略缓存一致性
Java通配符看这一篇就够

AQS 原理

AQS 是一个 java 层面实现的同步框架,借助 AQS 可以实现很多锁机制同步器等,AQS 底层借助于 unsafe 提供的 cas 接口park & unpark 接口。
下面我们基于 AQS 实现一个自定义阻塞式锁,实现 Lock 接口规定的阻塞式获取锁可打断阻塞式获取锁非阻塞式获取锁可打断可超时阻塞式获取锁释放锁方法,并详细介绍 AQS 在整个过程中是如何工作的。
如下所示,可以看到定义了一个 MySync 成员内部类实现了 AQS 抽象父类并重写了 tryAcquiretryRelease 和 isHeldExclusively 方法,AQSLock 类实现了 Lock 接口并持有一个 MySync 对象,实现了 Lock 接口的几种方法。

package org.howard1209a.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

public class AQSLock implements Lock {
    private MySync sync = new MySync();

    private class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

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

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

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

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

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

    @Override
    public Condition newCondition() {
        return null;
    }
}

lock 方法

调用了 AQS 的 acquire 方法
image.png
首先调了我们自己实现的 tryAcquire 方法,该方法是尝试获取锁,可以看到尝试 cas state,保证了只有一个线程可以获取到锁,该线程会将 exclusiveOwnerThread 设置为自己
image.png
返回 acquire 方法,获取到锁的线程可以直接返回,没获取到锁的线程会进入 addWaiter 方法,该方法是创建一个当前线程对应的节点,并让节点进队。tail 是 AQS 的一个属性,最开始是 null,因此直接进入 enq 方法
image.png
tail 为 null,因此会创建一个新的 node 并 cas 这个 tail 属性,保证多线程唯一创建 tail,这里是一个循环,当 tail 有节点时就会把 node 的前置节点设置为 tail 节点,然后** cas tail 为 node**,该操作只有一个线程会成功,其他线程失败后会再重试最后所有的线程都会成功将 node 节点加入到队列中。简单来说就是 enq 方法利用 cas 操作完成了多线程并发入队
image.png
后续 tail 不为 null 之后,addWaiter 方法会先尝试一次进队,如果失败再进 enq 方法。
image.png
addWaiter 方法构造节点并进队之后,就会进入 acquireQueued 方法,node.predecessor 方法会获取 node 节点的前一个节点,可以看到如果是队列中的第一个节点(除了 head 节点之外的),会尝试再次 tryAcquire 获取一次锁,如果获取成功会直接返回。如果没有获取到锁或者不是第一个节点,就会调用 shouldParkAfterFailedAcquire 方法判断是否要阻塞,这个方法我们不关注,不妨认为永远返回 true,这样就会进入 parkAndCheckInterrupt 方法
image.png
parkAndCheckInterrupt 方法比较简单,利用 LockSupport.park 阻塞当前线程,这里恢复调用的方式有两种,一个是当其他线程释放锁的时候会 unpark,另一个是 interrupt 打断
image.png
返回 acquireQueued 方法,一旦阻塞恢复调度之后,会循环执行之前的过程,只有第一个节点会尝试再次获取锁,注意虽然是第一个节点,但这并不意味着是公平锁,因为某个其他线程释放锁后,有可能有新的线程调用 tryAcquire 方法抢先获取到锁。
接下里我们看 unlock 释放锁接口,调用了 AQS 类的 release 方法
image.png
首先调 tryRelease 方法,这是我们自定义的方法
image.png
首先将 exclusiveOwnerThread 设置为 null,然后修改状态为 0,state 是 AQS 类的一个 int 属性,因此写入是原子的。
image.png
返回 release 方法,在释放锁后会判断如果 head 不为 null 时,会调用 unparkSuccessor 方法,可以看到获取了 head 的 next 节点(也就是第一个节点),然后** unpark 唤醒它**。
image.png

lockInterruptibly 方法

阻塞式获取锁但可打断,可以看到 acquireInterruptibly 会首先判断一下当前线程的打断标志位,如果打断标志位为 true 就直接抛异常,否则尝试获取锁,获取成功直接返回,失败进 doAcquireInterruptibly 方法
image.png
doAcquireInterruptibly 方法就和之前基本一致了,不同的是多了一个抛异常的动作
image.png

tryLock 方法

尝试一次获取锁没啥可说的,主要讲一下 tryLock(long time, TimeUnit unit) 方法,阻塞式获取锁,可打断可超时退出。
仍然先判断一下打断标志位
image.png
和之前的区别主要在于,如果 park 被打断了会直接抛出异常,阻塞操作利用 LockSupport.parkNanos 阻塞特定时间,超时恢复调度后下一次循环就会返回。
image.png

ReentrantLock 原理

上述实现的 AQSLock 其实就是参照了 ReentrantLock 的实现,我们简单讲一下 ReentrantLock 阻塞式获取锁和释放锁的过程

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    your code...
} finally {
    lock.unlock();
}

lock

ReentrantLock 有两个 AQS 实现类,分别是 FairSync 和 NonfairSync,默认使用的 NonfairSync 非公平实现
可以看到也是先 cas state,成功后设置 exclusiveOwnerThread 为当前线程,失败就进 AQS 的 acquire 方法
image.png
acquire 方法和之前一样
image.png
看看 NonfairSync 对 tryAcquire 的实现,如果 state 为 0 还是之前的 cas 逻辑,如果 state 不为 0 说明锁当前被一个线程占有,因为 ReentrantLock 是可重入的,所以如果锁属于当前线程会将本次 acquire 的值叠加在 state 上然后返回获取锁成功。
image.png
后面就和之前一样了
image.png

unlock 方法

unlock 方法和我们自己实现的 AQSLock 类的区别在于 tryRelease 方法,由于是可重入锁,所以要用 state 减去 releases 值,如果为 0 才释放锁,否则只是更新 state 的值
image.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值