独占模式和共享模式的方法并没有实现具体的加锁、释放锁逻辑,AQS中只是定义了加锁、释放锁的抽象方法 。
留给子类实现的抽象方法:
// 加独占锁protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}// 释放独占锁protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}// 加共享锁protected int tryAcquireShared(int arg) {throw new UnsupportedOperationException();}// 释放共享锁protected boolean tryReleaseShared(int arg) {throw new UnsupportedOperationException();}// 判断是否是当前线程正在持有锁protected boolean isHeldExclusively() {throw new UnsupportedOperationException();}
这里就用到了设计模式中的模板模式,父类AQS定义了加锁、释放锁的流程,子类ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier负责实现具体的加锁、释放锁逻辑 。
这不是个面试知识点吗?
面试官再问你,你看过哪些框架源码使用到了设计模式?
你就可以回答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程序中占用cpu最高的线程堆栈信息
- Java8新特性—四大内置函数式接口
- 2 java安全之CC1浅学
- JVM调优工具使用手册
- 记一次多个Java Agent同时使用的类增强冲突问题及分析
- Java使用lamda表达式简化代码
- 1 java安全之CC1浅学
- SpringBoot 01: JavaConfig + @ImportResource + @PropertyResource
- 一 OpenMP 教程 深入人剖析 OpenMP reduction 子句
- 1 Java安全之反序列化