[隐匿撕源码] 从ReentrantLock剖析AQS

Ady ·
更新时间:2024-09-21
· 774 次阅读

前言

鼠年新的工作日开始了,新的一年新的工作学习,第一个工作日给自己定一些要求吧。
想写一个系列【隐匿撕源码】 剖析各种经典框架,工具,JDK的源码提升自己,同时给大家分享一些心得。这篇文章就作为开端,干就完事了。

从ReentrantLock开始

ReentrantLock 是JDK给我们提供的显示锁 在功能上远远强于synchronized

是功能上 不是性能上,随着JDK版本的一代又一代升级 synchronized的性能已经远远不是以前的重量级锁那么沉重

ReentrantLock源码

我们来看ReentrantLock的构造方法 有两种 传入boolean值 选择此锁是否是公平锁
默认是非公平锁,性能高 可以插队

public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }

创建了公平sync或者非公平sync之后 赋值给了一个变量 我们看一下这个Sync到底是何方神圣
源码如图
在这里插入图片描述
看到这里我们发现Sync是一个抽象静态内部类 他继承了一个类叫AbstractQueuedSynchronizer 这也就是我们标题的AQS
先不管AQS是什么 看一下这个类的继承结构
在这里插入图片描述
所以我们得出一个结论 RenntrantLock的所有锁行为都是基于这个Sync实现的 而这个Sync又是根据AQS实现的,那我们就真正进入AQS中探索

AbstractQueuedSynchronizer

先做总结,对AQS有一个清晰的认识:
AQS是一个同步队列,是一个双向链表。
维护了这样一个同步队列
在这里插入图片描述
他使用了模板方法设计模式 提供了大量模板方法
分为两种 一种是模板 一种是需要子类覆盖的流程方法
如果不懂模板方法的小伙伴需要补充一些设计模式的知识
下面我分类 一一列举AQS中模板方法的源码并附上注释

模板方法 独占式: //获取同步状态() public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } //获取同步状态 (获取同步状态的时候可以响应中断) public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } // 响应中断的同时具有等待超时功能 public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); } //独占释放锁 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } 共享式 //共享获取同步状态 public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } //共享获取同步状态(可以响应中断) public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) = 0 || doAcquireSharedNanos(arg, nanosTimeout); } //共享释放 public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } 流程方法(需要AQS子类去覆盖的) //独占式获取锁 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } //共享获取 protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } //独占释放 protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } //共享释放 protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } //表示此同步器是否处于独占模式 protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); } 提一下final 的状态方法 //这是同步状态标志位 private volatile int state; //因为是volatile修饰的 所以不需要考虑获取的锁 protected final int getState() { return state; } //这是有原子性隐患的set方法 protected final void setState(int newState) { state = newState; } //CAS原子操作的set方法 protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }

唔 如果不知道volatile和CAS 的需要补充一些多线程知识 后期我会加入这块博客 暂时木有

其实AQS就是这么多模板方法 有了这些方法就可以随心所欲的实现自己想要的功能的锁了

调用关系如图
在这里插入图片描述

再看ReentrantLock

我们知道了AQS的模式 ,那么这次再看ReentrantLock里面的Sync,NonfairSync,FairSync是如何实现锁的就容易很多

Sync: abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //如果标记为是0 那么设置标记位 同时把当前线程记录 //因为是可重入锁 所以只需要记录线程就可以 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //如果标记为不是0 那么说明已经有人拿到了锁 判断拿到锁的是否是当前线程 //如果是 记录重入次数 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; //这里是重入次数不能大于 int最大值 。。 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // 如果标记为不是0 而且拿到锁的不是当前线程 则获取锁失败 return false; } //释放锁 protected final boolean tryRelease(int releases) { int c = getState() - releases; //如果当前线程不是拿到锁的线程 则抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //如果计算之后是0 说明可以释放锁 设置当前线程为null 即可 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); 设置state return free; } protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } //Condition类 final ConditionObject newCondition() { return new ConditionObject(); } final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } final boolean isLocked() { return getState() != 0; } } NonfairSync static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { //如果是未上锁状态(0) 则更改成1 并写入当前线程 //这段代码公平锁是没有的 因为非公平锁是可以插队 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); //如果不是 cas失败 那就尝试获取锁 else acquire(1); } protected final boolean tryAcquire(int acquires) { //Sync源码中有 return nonfairTryAcquire(acquires); } } FairSync static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } //公平锁上锁 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //如果当前锁没上锁 if (c == 0) { //hasQueuedPredecessors 这个方法确定当前线程必须是节点的头节点 //实现了公平锁 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //如果当前线程是执行线程 更新状态 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }

这样ReentrantLock的公平锁和非公平锁大致思路已经说完了
源码中的思路我都用注释的方式写在代码中了
接下来我们仿照Lock实现一个 可以同时有两个线程访问的锁:TwinsLock

自定义TwinsLock public class TwinsLock implements Lock{ private static final class Sync extends AbstractQueuedSynchronizer{ Sync(int count){ if (count <= 0) { throw new IllegalArgumentException("count must large zere"); } setState(count); } @Override protected int tryAcquireShared(int reduceCount) { for(;;){ int current = getState(); System.out.println(Thread.currentThread().getName()+"尝试获取锁"); int newCount = current - reduceCount; if (newCount<0 || compareAndSetState(current,newCount)) { String info = newCount 0; } //忽略 不是重点 @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; } //忽略 不是重点 @Override public Condition newCondition() { return null; } public static void main(String[] args) throws InterruptedException { final Lock lock = new TwinsLock(); class Worker extends Thread{ @Override public void run() { while(true){ lock.lock(); System.out.println("["+Thread.currentThread().getName()+"]开始执行============"); try{ Thread.sleep(1000); String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss")); System.out.println(now+" 线程["+Thread.currentThread().getName()+"]正在等待"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally{ lock.unlock(); } } } } //启动10个线程 for (int i = 0; i < 10; i++) { Worker w = new Worker(); w.start(); } } }

执行结果:
在这里插入图片描述

总结

AQS使用一个同步队列 使用FIFO 先入先出
我们可以基于AQS实现各种各样的花哨功能的锁
其中ReentrantLock就是我们常用的
而且在使用过程中我们完全感受不到AQS的存在
是一个非常棒的设计
并发工具 如果闭锁 CyclicBarrier Semaphore等等都是基于AQS实现的
tip:其中CyclicBarrier依赖的是ReentrantLock 所以也算是依赖AQS


作者:隐匿hide



reentrantlock 源码

需要 登录 后方可回复, 如果你还没有账号请 注册新账号