AQS和ReentrantLock的关系

ReentrantLock主要由li大师编写的可重入锁,可以说当时是为了替代synchronized而诞生的,因为synchronized当时是一个重量级锁,消耗性能,可能造成性能的急剧降低,随着后来synchronized的不断的优化,性能逐渐超过ReentrantLock,而ReentrantLock,ReentrantReadWriteLock等锁底层都是基于AQS来实现的。
在这里插入图片描述lock.unlock() 需要写在finally块中。
synchronized是可以作用与方法,类和代码块,是一个自动加锁和释放锁的。
在这里插入图片描述我们来看上面的图。说白了,ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。

🐖ReentrantLock加锁和释放锁的底层原理
好了,那么现在如果有一个线程过来尝试用ReentrantLock的lock()方法进行加锁,会发生什么事情呢?
很简单,这个AQS对象内部有一个核心的变量叫做state,是int类型的,代表了加锁的状态。初始状态下,这个state的值是0。 另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。
在这里插入图片描述
接着线程1跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操作将state值从0变为1。
如果之前没人加过锁,那么state的值肯定是0,此时线程1就可以加锁成功。
一旦线程1加锁成功了之后,就可以设置当前加锁线程是自己。所以大家看下面的图,就是线程1跑过来加锁的一个过程。
在这里插入图片描述
其实看到这儿,大家应该对所谓的AQS有感觉了。说白了,就是并发包里的一个核心组件,里面有state变星、加锁线程变量等核心的东西,维护了加锁状态。

你会发现,ReentrantLock这种东西只是一个外层的API,内核中的锁机制实现都是依赖AQs组件的。这个ReentrantLock之所以用Reentrant打头,意思就是他是一个可重入锁。

可重入锁的意思,就是你可以对一个ReentrantLock对象多次执行lock)加锁和unlock()释放锁,也就是可以对一个锁加多次,叫做可重入加锁。

大家看明白了那个state变量之后,就知道了如何进行可重入加锁!

其实每次线程1可重入加锁一次,会判断一下当前加锁线程就是自己,那么他自己就可以可重入多次加锁,每次加锁就是把state的值给累加1,别的没啥变化。

接着,如果线程1加锁了之后,线程2跑过来加锁会怎么样呢?

我们来看看锁的互斥是如何实现的?线程2跑过来一下看到,哎呀! state的值不是O啊?所以CAS操作将state从0变为1的过程会失败,因为state的值当前为1,说明已经有人加锁了!

接着线程2会看一下,是不是自己之前加的锁啊?当然不是了,“加锁线程"这个变量明确记录了是线程1占用了这个锁,所以线程2此时就是加锁失败。

给大家来一张图,一起来感受—下这个过程:
在这里插入图片描述接着,线程2会将自己放入AQS中的一个等待队列,因为自己尝试加锁失败了,此时就要将自己放入队列中来等待,等待线程1释放锁之后,自己就可以重新尝试加锁了

所以大家可以看到,AQS是如此的核心!AQS内部还有一个等待队列,专门放那些加锁失败的线程!同样,给大家来一张图,一起感受—下:

在这里插入图片描述接着,线程1在执行完自己的业务逻辑代码之后,就会释放锁!他释放锁的过程非常的简单,就是将AQS内的state变量的值递减1,如果state值为0,则彻底释放锁,会将“加锁线程"变量也设置为null!

如图整个过程
在这里插入图片描述接下来,会从等待队列的队头唤醒线程2重新尝试加锁 。

好!线程2现在就重新尝试加锁,这时还是用CAS操作将state从0变为1,此时就会成功,成功之后代表加锁成功,就会将state设置为1。

此外,还要把"加锁线程"设置为线程2自己,同时线程2自己就从等待队列中出队了。最后再来—张图,大家来看看这个过程。
在这里插入图片描述
总而言之,其实一句话总结AQS就是一个并发包的基础组件,用来实现各种锁,各种同步组件的。它包含了state变量、加锁线程、等待队列等并发中的核心组件。

AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法:
在这里插入图片描述不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS 已经在上层已经帮我们实现好了。ReentrantLock ,Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock ,ReentrantReadWriteLock 等都是以AQS为基础实现的。

---- 以 ReentrantLock 为例,state 初始化为 0,表示未锁定状态。A 线程 lock()时,会调用 tryAcquire()独占该锁并将 state+1。此后,其他线程再 tryAcquire()时就会失败,直到 A 线程 unlock()到 state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证 state 是能回到零态的。

----再以 CountDownLatch 以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后 countDown()一次,state 会 CAS(Compare and Swap)减 1。等到所有子线程都执行完后(即 state=0),会 unpark()主调用线程,然后主调用线程就会从 await()函数返回,继续后余动作。

----一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryReleasetryAcquireShared-tryReleaseShared中的一种即可。但 AQS 也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock

AQS(AbstractQueuedSynchronizer)ReentrantLockJava并发包中两种不同的同步机制。 1. **工作原理不同**: - AQS是一种底层的、无锁的同步结构,依赖于一系列的状态标志来控制同步流程。它主要用于构建更复杂的同步工具,如Semaphore、CountDownLatch等。 - ReentrantLock则是一个内置锁机制,拥有显式的锁unlock操作,允许线程多次获取锁(reentrant,即自含回路),并且有公平不公平两种获取策略。 2. **并发性能**: - AQS由于其内部设计,通常适用于轻量级的同步场景,避免了锁争抢导致的频繁上下文切换。但在竞争激烈的环境中,AQS可能会造成线程堆积。 - ReentrantLock提供了可中断定时锁等功能,适合需要更多定制化同步需求的应用。 3. **使用体验**: - AQS的API相对简单,通常通过回调函数的方式来处理同步事件,开发者需要自己编写同步代码。 - ReentrantLock则提供了更为直观易于理解的API,支持更多的同步操作,如检查是否已获取锁、超时获取锁等。 4. **公平性**: - AQS默认是非公平的,最近的请求优先被服务;而ReentrantLock可以设置为公平模式,保证所有线程按顺序获取锁。 5. **中断与响应**: - ReentrantLock支持中断线程,AQS则没有这种特性。 总的来说,AQS更适合追求高性能、灵活性自定义的场合,而ReentrantLock则是为了提供更丰富的功能易用性。选择哪一个取决于具体的项目需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值