ReentrantLock早期是因为synchronized太重而产生的锁,synchronized作为传统基础锁,后期经过改进,性能有很大改善,reentrantLock结合了CAS+自旋+AQS+volatile+park等,CAS和volatile的原理知识我前面的文章总结过,今天说一下AQS(后续总结还会更新到此文档,毕竟源码很头疼)。
首先说明几点:
1:持有锁的线程永远不再等待队列中
2:reentrantlock修改的是AQS对象的锁状态,并且该状态和线程(Node内部类)绑定,Synchronized修改的是对象的对象头markword信息,对象头里绑定线程信息
3:AQS是抽象类,既然是类就有结构
node内部类(构成双向链表)属性
AQS属性
双向链表图示:
公平锁:
优点:所有的线程都能得到资源,分布均匀。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁:
多个线程去获取锁的时候,会直接去尝试获取trylAcquire,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必去唤醒所有线程,会减少唤起线程的数量。
缺点:可能存在一直等待的线程。
reentrantlock默认非公平锁,可以通过构造参数指定。
我们看一下加锁方法流程:
两种实现方式
先说下公平实现:
重要的三部曲
尝试获取锁
reentrantlock是悲观锁:在加锁的时候是设置独占线程。互斥锁,认为在自己操作数据的时候会打扰。setExclusiveOwnerThread(current);
是否需要排队
并发考虑,我们在入队的时候,线程也是不断在执行,判断条件也在不断变化
添加独占线程节点
添加节点的多种场景
对入队节点操作,原则,能获取锁就不park,尽量不park
抢占到锁,准备工作
AQS为什么是双链表?
在入队的时候,需要判断前一个等待节点是否为正常等待节点,如果不是的话会将当前节点往前移动,此操作单链表做不到
公平和非公平的主要区别是,公平锁要排队,非公平锁先抢占锁,抢占不到在排队。
公平锁:尝试获取锁的时候(如果锁状态为0),进来先看要不要排队,如果队列为空(头尾相等),
cas抢占,抢占失败,进队列,如果队列不为空(>=两个节点),直接排队
非公平锁:新线程直接CAS抢占锁,没抢到在去尝试获取锁(如果锁状态为0)
我总结了一张流程图:
AQS加锁原理设计的很巧妙,后期我会继续学习,有更深的体会,会继续和大家分享。