一文带你看懂Java中的Lock锁底层AQS到底是如何实现的(一)

pivoteic
发布于 2022-6-16 17:46
浏览
0收藏

 

相信大家对Java中的Lock锁应该不会陌生,比如ReentrantLock,锁主要是用来解决解决多线程运行访问共享资源时的线程安全问题。那你是不是很好奇,这些Lock锁api是如何实现的呢?本文就是来探讨一下这些Lock锁底层的AQS(AbstractQueuedSynchronizer)到底是如何实现的。

 

本文是基于ReentrantLock来讲解,ReentrantLock加锁只是对AQS的api的调用,底层的锁的状态(state)和其他线程等待(Node双向链表)的过程其实是由AQS来维护的

 

加锁

 

我们先来看看加锁的过程,先看源码,然后模拟两个线程来加锁的过程。

一文带你看懂Java中的Lock锁底层AQS到底是如何实现的(一)-鸿蒙开发者社区

上图是ReentrantLock的部分实现。里面有一个Sync的内部类的实例变量,这个Sync内部类继承自AQS,Sync子类就包括公平锁和非公平锁的实现。说白了其实ReentrantLock是通过Sync的子类来实现加锁。

 

我们就来看一下Sync的非公平锁的实现NonfairSync。

一文带你看懂Java中的Lock锁底层AQS到底是如何实现的(一)-鸿蒙开发者社区

重写了它的lock加锁方法,在实现中因为是非公平的,所以一进来会先通过cas尝试将AQS类的state参数改为1,直接尝试加锁。如果尝试加锁失败会调用AQS的acquire方法继续尝试加锁。

 

假设这里有个线程1先来调用lock方法,那么此时没有人加锁,那么就通过CAS操作,将AQS中的state中的变量由0改为1,代表有人来加锁,然后将加锁的线程设置为自己如图。

一文带你看懂Java中的Lock锁底层AQS到底是如何实现的(一)-鸿蒙开发者社区

那么此时有另一个线程2来加锁,发现通过CAS操作会失败,因为state已经被设置为1了,线程线程2就会设置失败,那么此时就会走else,调用AQS的acquire方法继续尝试加锁。

一文带你看懂Java中的Lock锁底层AQS到底是如何实现的(一)-鸿蒙开发者社区

进入到acquire会先调用tryAcquire再次尝试加锁,而这个tryAcquire方法AQS其实是没有什么实现的,会调用到NonfairSync里面的tryAcquire,而tryAcquire实际会调用到Sync内部类里面的nonfairTryAcquire非公平尝试加锁方法。

一文带你看懂Java中的Lock锁底层AQS到底是如何实现的(一)-鸿蒙开发者社区一文带你看懂Java中的Lock锁底层AQS到底是如何实现的(一)-鸿蒙开发者社区

先获取锁的状态,判断锁的状态是不是等于0,等于0说明没人加锁,可以尝试去加,如果被加锁了,就会走else if,else if会判断加锁的线程是不是当前线程,是的话就给state 加 1,代表当前线程加了2次锁,就是可重入锁的意思(所谓的可重入就是代表一个线程可以多次获取到锁,只是将state 设置为多次,当线程多次释放锁之后,将state 设置为0才代表当前线程完全释放了锁)。

 

这里所有的条件假设都不成立。也就是线程2尝试加锁的时候,线程1并没有释放锁,那么这个方法就会返回false。

 

接下来就会走到addWaiter方法,这个方法很重要,就是将当前线程封装成一个Node,然后将这个Node放入双向链表中。addWaiter先根据指定模式创建指定的node节点,因为ReentrantLock是独占模式,所以传进去的EXCLUSIVE,这里通过当前线程和模式传入,初始化一个双向node节点,获取最后一个节点,根据最后一个节点是否存在来操作当前节点的父级。如果尾节点不存在会去调用enq去初始化

一文带你看懂Java中的Lock锁底层AQS到底是如何实现的(一)-鸿蒙开发者社区

文章转自公众号:三友的java日记

标签
已于2022-6-16 17:46:43修改
收藏
回复
举报
回复
    相关推荐