这不是个面试知识点吗?
面试官再问你,你看过哪些框架源码使用到了设计模式?
你就可以回答AQS源码中用到了模板模式 , 巴拉巴拉,妥妥的加分项!
文章插图
4. AQS源码剖析整个加锁流程如下:
文章插图
先看一下加锁方法的源码:
4.1 加锁
// 加锁方法 , 传参是1public final void acquire(int arg) {// 1. 首先尝试获取锁,如果获取成功,则设置state+1,exclusiveOwnerThread=currentThread(留给子类实现)if (!tryAcquire(arg) &&// 2. 如果没有获取成功,把线程组装成Node节点,追加到同步队列末尾acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {// 3. 加入同步队列后,将自己挂起selfInterrupt();}}
再看一下addWaiter方法源码,作用就是把线程组装成Node节点,追加到同步队列末尾 。// 追加到同步队列末尾,传参是共享模式or排他模式private Node addWaiter(Node mode) {// 1. 组装成Node节点Node node = new Node(Thread.currentThread(), mode);Node pred = tail;if (pred != null) {node.prev = pred;// 2. 在多线程竞争不激烈的情况下,通过CAS方法追加到同步队列末尾if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 3. 在多线程竞争激烈的情况下,使用死循环保证追加到同步队列末尾enq(node);return node;}// 创建Node节点,传参是线程,共享模式or排他模式Node(Thread thread, Node mode) {this.thread = thread;this.nextWaiter = mode;}// 通过死循环的方式,追加到同步队列末尾private Node enq(final Node node) {for (; ; ) {Node t = tail;if (t == null) {if (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}
再看一下addWaiter方法外层的acquireQueued方法,作用就是:- 在追加到同步队列末尾后,再判断一下前驱节点是不是头节点 。如果是,说明是第一个加入同步队列的,就再去尝试获取锁 。
- 如果获取锁成功,就把自己设置成头节点 。
- 如果前驱节点不是头节点,或者获取锁失败 , 就逆序遍历同步队列,找到可以将自己唤醒的节点 。
- 最后才放心地将自己挂起
// 追加到同步队列末尾后,再次尝试获取锁final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (; ; ) {// 1. 找到前驱节点final Node p = node.predecessor();// 2. 如果前驱节点是头结点,就再次尝试获取锁if (p == head && tryAcquire(arg)) {// 3. 获取锁成功后,把自己设置为头节点setHead(node);p.next = null;failed = false;return interrupted;}// 4. 如果还是没有获取到锁,找到可以将自己唤醒的节点if (shouldParkAfterFailedAcquire(p, node) &&// 5. 最后才放心地将自己挂起parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
【硬核剖析Java锁底层AQS源码,深入理解底层架构设计】再看一下shouldParkAfterFailedAcquire方法,是怎么找到将自己唤醒的节点的?为什么要找这个节点?// 加入同步队列后 , 找到能将自己唤醒的节点private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;// 1. 如果前驱节点的状态已经是SIGNAL状态(释放锁后,需要唤醒后继节点),就无需操作了if (ws == Node.SIGNAL)return true;// 2. 如果前驱节点的状态是已取消,就继续向前遍历if (ws > 0) {do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 3. 找到了不是取消状态的节点,把该节点状态设置成SIGNALcompareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}
从代码中可以很清楚的看到,目的就是为了找到不是取消状态的节点,并把该节点的状态设置成SIGNAL 。状态是SIGNAL的节点 , 释放锁后,需要唤醒其后继节点 。
简单理解就是:小弟初来乍到 , 特意来知会老大一声,有好事,多通知小弟 。
再看一下释放锁的逻辑 。
4.2 释放锁释放锁的流程如下:
文章插图
释放锁的代码逻辑比较简单:
// 释放锁public final boolean release(int arg) {// 1. 先尝试释放锁,如果时候成功,则设置state-1 , exclusiveOwnerThread=null(由子类实现)if (tryRelease(arg)) {Node h = head;// 2. 如果同步队列中还有其他节点 , 就唤醒下一个节点if (h != null && h.waitStatus != 0)// 3. 唤醒其后继节点unparkSuccessor(h);return true;}return false;}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 定位java程序中占用cpu最高的线程堆栈信息
- Java8新特性—四大内置函数式接口
- 2 java安全之CC1浅学
- JVM调优工具使用手册
- 记一次多个Java Agent同时使用的类增强冲突问题及分析
- Java使用lamda表达式简化代码
- 1 java安全之CC1浅学
- SpringBoot 01: JavaConfig + @ImportResource + @PropertyResource
- 一 OpenMP 教程 深入人剖析 OpenMP reduction 子句
- 1 Java安全之反序列化