前排提示:
ReentrantLock
(后文简称RL
), 非公平锁,JDK 1.8
. 本文不涉及太高深原理, 如题所致, 需要的可以看看安琪拉的博客
1. 开篇
所谓AQS(AbstractQueuedSynchronizer)
,就是提供了一个框架, 用于实现依赖于先进先出(FIFO
)等待队列的阻塞锁和相关的同步器. 拥有多个实现, RL
便是其中一个, 在面试中也是常问的. 网上大多文章都千篇一律, 甚至有的是直接复制粘贴. 还是得请自阅读, 故做此记录.
AQS
自身没有实现同步组件, 对外提供模板方法共自定义组件使用. 官方推荐是由非公共的内部类去实现. 我认为这样的好处就是将相同的逻辑关系组织在一起, 对外界是隐蔽的.
RL
提供了两个AQS
实现. 默认采用非公平方式创建. 学习时建议心里带着两个线程在进行方法竞争.
2. 正文
直接从RL
非公平锁加锁开始, 贴一些关键代码, 利用IDE
可以很方便知道使用的是哪个方法.
接下来直接进入正题, 从加锁开始:
上面利用CAS
方式设置volatile
的值(这部分知识可以查阅多线程相关资料), 如果成功将state
成功设置为1则加锁成功, 并设置目前独占线程为加锁线程.
没有获取到锁的进程先会执行下列方法:
再次尝试能不能获取到锁, 可能上一个进程已经释放了锁.
else
实现的就是重入锁逻辑, 这部分也很简单, 只是简单的增加计数器.
如果此时还没拿到锁(运气差), 那就着手入队操作了(链表实现的队列):
首先构建了一个Node
节点,如下图1-1所示. 下面这个if
使用CAS
将当前节点添加至队列尾部, 以减少后续死循环所带来的性能消耗(剧透了), 如果替换成功就是下图1-2所示, 图中红色为数据, 1-1为真实数据, 1-2为假数据.
但是有个细节需要注意: node.prev = pred
这句代码是在CAS
之前执行, 也就是就算该线程没在这里添加到队尾, 该属性设置依然有效. 不过这对后面也没有影响😄.
没能在第一时间加入队尾, 那只能跟随大部队进入
enq
了:
上面的这段死循环代码相信很好理解, 无非就是用
CAS
将节点添加至队尾. 注意点: 如果队列没有进行初始化, 则会创建一个空Node
进行初始化, 多个线程执行就是下面这种效果:
构建完队列, 那就是出队啦! 不然队列的线程都不会执行. 看这个代码得对应上面图1-3,不然容易乱:
node.predecessor()
就是获取该节点的上一个节点, 对应上面的图就是A线程的上一个, B线程的上一个...以此类推. 后面这个if很奇妙, 如果你的上一个节点是head
节点, 说明你就是第一个需要运行的线程, 这时候就会尝试去获取锁. 获取成功后会将获得的线程变为head
节点, 把prev,thread
置为null
, 同时把原head
节点的next
置空, 这样原head
就彻底为null
了. 这里有点需要注意, 至始至终运行节点的next
都没有置空(伏笔1):
当然, 上面是一切顺利的情况下, 那肯定还有老倒霉蛋的
prev
不是head
, 那就看下面的操作了:
waitStatus
表示该线程所处的状态, 通俗点讲就是该线程想要干什么. 以为第一次并没有设置waitSatatus
的值,所以默认都是0,所以第一次执行完应该是下面的状态:
然后返回
false
,继续下一次循环, 因为已经将waitStatus
设置为Node.SIGNAL
所以第二次循环时直接返回true. 大于0的情况是处理队列线程被中断的情况下, 将该节点移除队列, 这里不多说了. 在shouldParkAfterFailedAcquire
返回true后, 便会执行parkAndCheckInterrupt()
, 该方法会挂起当前线程. 所以到这里, 线程在没有获取到锁时总共尝试获取过三次锁. 第一次在首次获取时重试, 后面两次在最后的死循环中尝试获取.
以上便是锁竞争所出现的情况. 下面看看解锁.
上面这代码还是很好理解的, 就判断是否是同一线程, 然后设置一些状态标识. 其实这里也能看出他是非公平锁: 在设置完
state
的值后, 并没有唤醒后继节点(还在挂起中), 这时候如果有新线程进来便会抢占锁.
这就是最后一段解锁代码, 如果前面一段返回true, 代表该线程的锁已经释放完了, 可以唤起后续节点了.
h.waitStatus != 0
在前面是已经改为-1了, -1就代表着后继几点想要执行.
unparkSuccessor()
这个方法就是唤醒后继节点, 把上面的-1带进去就一目了然了. 上面溜下的伏笔就是在这里了, 获取next
的值, 然后进行唤醒!
以上便是文章所有内容.
3.杂谈
因为是基于API
层面的实现, 看AQS
源码并不会太难, 就是用了模板方法模式方法有点饶, 在阅读的时候需要聚精会神, 同时要变做好信息记录, 主要需要关注的还 它底层的技术, 比如volatile
底层原理之类的.