为方便理解,本文章以非公平锁ReentrantLock()为例作为突破讲解方法lock。
前置知识:JAVA AQS源码分析前置知识-CSDN博客
ReentrantLock的原理
Lock接口的实现类,基本都是通过聚合了一个队列同步器的子类完成线程访问控制的
从最简单的lock方法开始看看公平和非公平
公平锁和非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()-----公平锁加锁时判断等待队列中是否存在有效节点的方法
以非公平锁ReentrantLock()为例作为突破---方法lock()
对比公平锁和非公平锁的tryAcquire()方法的实现代码,其实差异就在于非公平锁获取锁时比公平锁中少了一个判断!hasQueuedPredecessors(),hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:
- 公平锁:公平锁讲究先来后到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入到等待队列中;
- 非公平锁:不管是否有等待队列,如果可以获取到锁,则立刻占有锁对象。也就是说队列的第一个排队线程苏醒后,不一定就是排头的这个线程获得锁,它还需要参加竞争锁(存在线程竞争的情况下),后来的线程可能不讲武德插队夺锁了。
源码解读
1. lock()
- 非公平锁,先试图直接抢占锁(调用compareAndSetState),若未抢占成功才调用acquire方法。
- 即:不管是否有等待队列,都先进行锁的抢夺
- 公平锁,直接调用acquire方法。
2.acquire()
- 类似一个责任链,先尝试获取锁(tryAcquire),若失败则加入队列(addWriter)并坐稳队列(acquireQueued)
- return false:继续推进条件,走下一个方法
- return true:结束
2.1. tryAcquire(arg)
- 流程:先判断锁状态,若为0(未锁定)则尝试争抢锁,否则判断当前持有锁的是否是当前线程,否则返回fase
- 对比:公平锁和非公平的的区别是是否进行!hasQueuedPredecessors() 的判断,即是否判断队列中是否有等待的节点。
- 非公平锁:
- 公平锁:
2.2.addwaiter
- 从enq方法中我们可以知道在双向链表中,第一个节点为虚节点(也叫做哨兵节点),其实不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的
- 假如此时有线程C进入
2.3.acquireQueued(addWeiter(Node.EXCLUSIVE), arg)
3. unlock()
总览:
-
tryRelease
4.其他线程unlock后的同时其他线程的acquireQueued
5. acquireQueued执行失败或取消执行时