坑外话:从根本上说 ,如果你不懂AQS ,没有深入的理解AQS的设计艺术,那么你就谈不上真的意义上的懂得JUC编程
先了解一部分关于锁的基础知识
Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的
我们拿ReentrantLock 来看,内部聚合了一个Sync的子类
ReentrantLock中公平锁 和 非公平锁
公平锁 和 非公平 的区别 :
- 可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:
- hasQueuedPredecessors()
- hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法
我的理解是 :
- 在唤醒线程的时候,公平锁采用的策略 是根据同步队列中,按序的唤醒一个线程,让它去获取锁资源;
- 在唤醒线程的时候,非公平锁采用的策略 将同步队列中 所有的线程全部唤醒,让他们去争抢锁资源
下面我们通过ReentrantLock 中的lock () 和 unlock() 理解源码,主要看非公平锁的实现
lock ()
看非公平锁的实现
调用lock()方法时候
继续追踪源码 acquire()
一、 tryAcquire()
这个接口的目的是,让第一次抢不到锁的线程,再去尝试一次,采用是 尽量争抢,不要轻易阻塞的思想
追踪源码 发现,里面直接抛出异常
这里为什么直接抛出异常呢,这里是一种设计模式 叫 模板设计模式的典型运用,直接给你抛出异常限制你子类必须去实现这个钩子方法;
查看nofairTryAcquire 方法
二、addWaiter 方法
当tryAcquire 返回false ,取反后 条件继续判断就来到了 addWaiter 方法
这个方法的作用是让结点入队
那么第一种情况,当前队列中,没有元素,则需要初始化队列
注意:双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。 真正的第一个有数据的节点,是从第二个节点开始的
接下来我们看看怎么初始化队列
第二种情况,队列中已经有元素了
给大家看个图,应该能清晰不少
三、acquireQueued 方法
selfInterrupt 方法
让当前线程阻塞
unlock()
当锁空闲,则由AQS 负责去唤醒线程
查看源码
查看release
查看tryRelease
又是设计模式的思想,我们去查看他的子类具体实现
继续看里面唤醒方法 unparkSuccessor
一看本质上就是采用了LockSupport的unpark方法 ,那些处于等待的线程 就被唤醒
下面记录两个坑人的题哈
一、我相信你应该看过源码了,那么AQS里面有个变量叫State,它的值有几种?
3个状态:没占用是0,占用了是1,大于1是可重入锁
二、如果AB两个线程进来了以后,请问这个总共有多少个Node节点?
答案是3个 ,因为第一个是哨兵结点