走进 AQS 瞧一瞧看一看
并发中有一块很重要的东西就是AQS。接下来一周的目标就是它。
看复杂源码时,一眼望过去,这是什么?不要慌,像剥洋葱一样,一层层剥开(哥,喜欢"扒开"这个词)。
参考资源:
https://www.cnblogs.com/waterystone/p/4920797.html
https://javadoop.com/post/AbstractQueuedSynchronizer#toc4
一概述
大师Doug Lea依赖FIFO(First-in-first-out)等待队列,创建了AQS框架来实现锁和同步器的操作。AQS是LimitLatch,countDownLacth,ReentrantLock,etc 这些类的基础。它们都是继承了AQS类,继承的时候,protected的方法根据业务需要必须重写,也就是tryAcquire,tryRelease,tryAcquireShared,tryReleaseShared,isHeldExclusively中的一部分或是全部。
二AQS结构
1.我们看看AQS的属性:
静态内部类 Node (为什么要写静态内部类,个人觉得,Node是链表结构,在这里作为阻塞队列来使用,只有单独一个地方(AQS中)使用,所以写成静态内部类,也提高了封装性)
// 头结点
private transient volatile Node head;
// 尾结点
private transient volatile Node tail;
// 资源状态,等于0:没有被任何thread占有;大于0:资源已经被其他线程占有
private volatile int state;
2.Node的内部构造
Node类至关重要,我懒了,贴一下代码,发现里面的注释真的很详细,就不再翻译一遍了。
/** Marker to indicate a node is waiting in shared mode */
// 共享模式
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
// 独占模式
static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
// 在这里,大家注意一下。waitStatus 值有1,-1,-2,中间越过了0.
// 其实,waitStatus 的值,有0 这种情况,初始值为0
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3; /**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus; volatile Node prev;
*/
volatile Node next; /**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread; Node nextWaiter;
接下来,进入到AQS源码分析环节。
概述中提到过下面这几个方法tryAcquire,tryRelease,tryAcquireShared,tryReleaseShared
它们分别是独占锁的获取和释放,共享锁的获取和释放。
这里,我们从独占锁的获取开始讲起。
3.独占锁方法解析
3.1 acquire 方法
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
// 尝试获取锁,tryAcquire放回true,表示成功获取锁,就不会再往下走了。
public final void acquire(int arg) {
// 尝试获取锁失败,并且,acquireQueued成功,那么就会进入方法中,执行自我中断
// 接下来,开始剥洋葱,方法逐个分析解惑
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3.2 tryAcquire 方法
/**
* Attempts to acquire in exclusive mode. This method should query
* if the state of the object permits it to be acquired in the
* exclusive mode, and if so to acquire it.
*
* <p>This method is always invoked by the thread performing
* acquire. If this method reports failure, the acquire method
* may queue the thread, if it is not already queued, until it is
* signalled by a release from some other thread. This can be used
* to implement method {@link Lock#tryLock()}.
*
* <p>The default
* implementation throws {@link UnsupportedOperationException}.
*
* @param arg the acquire argument. This value is always the one
* passed to an acquire method, or is the value saved on entry
* to a condition wait. The value is otherwise uninterpreted
* and can represent anything you like.
* @return {@code true} if successful. Upon success, this object has
* been acquired.
* @throws IllegalMonitorStateException if acquiring would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if exclusive mode is not supported
*/
// 哇咔咔,这么长注释,不过别着急。慢慢看。
// 这个方法用来查询对象的state是否允许以独占方式来获取锁,如果可以,尝试获取。
// 接下里大家会疑问,为什么这个方法里面只有throw这一行代码。因为,这个方法需要在子类继承的时候需要被重写,这个就是设计模式中的模板方法。
// 同时,这个方法没有被做成abstract方法,因为,子类继承的时候为了自己的需求,只需要实现独占模式的方法或是共享模式的方法即可,不用都去实现。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
3.3 acquireQueued方法
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
// 线程挂起后,被解锁,就是在这个方法里实现的
// 方法返回结果:等待时,是否被中断
final boolean acquireQueued(final Node node, int arg) {
// failed true:表示没有拿到资源 false:表示成功拿到资源
boolean failed = true;
try {
// interrupted 是否被中断
boolean interrupted = false;
// 又一个自旋的使用哦
for (;;) {
// 获取前一个节点Node
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// 设置头结点,将原先的头结点与node节点的连接,断开
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 获取锁失败,是否应该挂起当前线程
// p 是前驱节点,node是当前线程节点
if (shouldParkAfterFailedAcquire(p, node) &&
// 获取锁失败,挂起线程的操作在下面方法里实施
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.3.1 shouldParkAfterFailedAcquire方法
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
// 这个方法用途:获取锁失败,判断是否需要挂起线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 等待状态 SIGNAL表示前驱节点状态正常,当前线程需要挂起,直接返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
// 这个节点已经让请求release的状态来标识,所以它可以安全的park了
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
// ws大于0的状态1,表示取消了排队。
// 如果取消了排队,接着再去找前一个,前一个也被取消了,就找前一个的前一个,总会有一个没被取消的。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
// else的情况,就是waitStatus 为0,-2,-3
// 用CAS将前驱节点状态设为-1(Node.SIGNAL)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
3.3.2 parkAndCheckInterrupt方法
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
// 在shouldParkAfterFailedAcquire返回true的时候,才会执行这个方法,这个方法的作用就是挂起线程
private final boolean parkAndCheckInterrupt() {
// 调用park方法,使线程挂起 (使用unpark方法来唤醒线程)
LockSupport.park(this);
// 查看线程是否被中断
return Thread.interrupted();
}
3.4 addWaiter 方法
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
// 将Node放进阻塞队列的末尾
private Node addWaiter(Node mode) {
// 生成一个node节点,保存当前的线程和占用模式(独占模式或是共享模式)
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 查询tail节点是否有值,有值代表的是此链表不为空
if (pred != null) {
// node相连接,很简单,就是改变指针指向,可以参考数据结构 链表
node.prev = pred;
// compareAndSetTail这个方法的操作就是比较赋值,经典的CAS方法,不明白的可以参照下面资源
// http://www.cnblogs.com/lihao007/p/8654787.html
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
3.4.1 eng()方法
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
// 来到这个方法有两种可能。一是等待线程队列为空,二是其他线程更新了队列
private Node enq(final Node node) {
// 又是自旋方法(乐观锁),AQS源码中出现多次,因为需要线程的不安全
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 将node放在队列最后,有线程竞争的话,排不上重排
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
独占模式获取锁,就分析到这里了。
4.release独占模式 释放锁
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
// 独占模式下,释放锁
public final boolean release(int arg) {
// tryRelease方法是模板方法,留给子类定义。
if (tryRelease(arg)) {
Node h = head;
// 首先判断阻塞队列是否为空。接下来,判断waitStatus的状态,0的状态是初始化是被赋予的值。只有是非0的状态,才说明head节点后面是有其它节点的。
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
4.1 unparkSuccessor
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); /*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 这里,是从队列末尾向前查找没有被取消的节点
// 这里,我当时对为什么从后向前查找有疑问,后来看了文章明白了。地址:https://javadoop.com/post/AbstractQueuedSynchronizer#toc4
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
就写到这里了,仍需努力,以后有想法了,慢慢补充,写的不对的地方,欢迎指正,共同探讨。
走进 AQS 瞧一瞧看一看的更多相关文章
- 瞧一瞧,看一看呐,用MVC+EF快速弄出一个CRUD,一行代码都不用写,真的一行代码都不用写!!!!
瞧一瞧,看一看呐用MVC+EF快速弄出一个CRUD,一行代码都不用写,真的一行代码都不用写!!!! 现在要写的呢就是,用MVC和EF弄出一个CRUD四个页面和一个列表页面的一个快速DEMO,当然是在不 ...
- 2015年4月27日---C语言:输出特殊图案,请在c环境中运行,看一看,Very Beautiful!
---恢复内容开始--- 题目:输出特殊图案,请在c环境中运行,看一看,Very Beautiful! 1.程序分析:字符共有256个.不同字符,图形不一样. 2.程序源代码: [code=c] #i ...
- Loj#6183. 看无可看
Loj#6183. 看无可看 题目描述 首先用特征根求出通项公式\(A_n=p\cdot 3^n+q\cdot(-1)^n\).通过给定的\(f_0,f_1\)可以解出\(p,q\). 然后我们要求的 ...
- 看无可看 分治FFT+特征值方程
题面: 看无可看(see.pas/cpp/c) 题目描述 “What’s left to see when our eyes won’t open?” “若彼此瞑目在即,是否终亦看无可看?” ---- ...
- Scrum模拟微信看一看“疫情专区”的敏捷开发过程
无论作为产品用户还是管理咨询顾问,都非常非常喜欢微信.自认感情比较克制属于“高冷”挂,但从很多方面都太佩服太崇拜张小龙了(新书里微信也会是最喜欢的案例之一,真的不只是一个产品而已,很多方面都太牛了). ...
- Mysql数据库优化技术之配置篇、索引篇 ( 必看 必看 转)
转自:Mysql数据库优化技术之配置篇.索引篇 ( 必看 必看 ) (一)减少数据库访问对于可以静态化的页面,尽可能静态化对一个动态页面中可以静态的局部,采用静态化部分数据可以生成XML,或者文本文件 ...
- 手写Spring AOP,快来瞧一瞧看一看撒!
目录 AOP分析 Advice实现 定义Advice接口 定义前置.后置.环绕和异常增强接口 Pointcut实现 定义PointCut接口 定义正则表达式的实现类:RegExpressionPoin ...
- 不能再被问住了!ReentrantLock 源码、画图一起看一看!
前言 在阅读完 JUC 包下的 AQS 源码之后,其中有很多疑问,最大的疑问就是 state 究竟是什么含义?并且 AQS 主要定义了队列的出入,但是获取资源.释放资源都是交给子类实现的,那子类是怎么 ...
- 今天做项目用到框架,关于angual,然后自己整理了一番,自己上网也看了看。
1. Angular 1.1. 库与框架的区别 jQuery:库 库一般都是封装了一些常用的方法 自己手动去调用这些方法,来完成我们的功能 $('#txt').val('我是小明'): $('div' ...
随机推荐
- day038 navicat pymysql
今日内容: 1.navicat 2.pymysql 1.navicat 需要掌握 #1. 测试+链接数据库 #2. 新建库 #3. 新建表,新增字段+类型+约束 #4. 设计表:外键 #5. 新建查询 ...
- .net core 之Hangfire任务调度
Hangfire可用作任务调度,类似延迟任务.队列任务.批量任务和定时任务等. 一.nuget Hangfire包 找到Hangfire.AspNetCore和Hangfire.SqlServer包, ...
- 4.6 C++抽象基类和纯虚成员函数
参考:http://www.weixueyuan.net/view/6376.html 总结: 在C++中,可以通过抽象基类来实现公共接口 纯虚成员函数没有函数体,只有函数声明,在纯虚函数声明结尾加上 ...
- Android开发---网格布局案例
Android开发---网格布局案例 效果图: 1.MainActivity.java package com.example.android_activity; import android.ap ...
- 90%会搞错的JavaScript闭包问题
由工作中演变而来的面试题 这是一个我工作当中的遇到的一个问题,似乎很有趣,就当做了一道题去面试,发现几乎没人能全部答对并说出原因,遂拿出来聊一聊吧. 先看题目代码: function fun(n,o) ...
- 运用HTML5+CSS3和CSS滤镜做的精美的登录界面
原始出处http://chenjinfei.blog.51cto.com/2965201/774865 <!DOCTYPE HTML> <html> <head> ...
- SDK Manager的使用
前言:SDK Manager就是一个Android软件开发工具包管理器,就像一个桥梁,连通本地和服务器,从服务器下载安卓开发所需工具到本地. 1.在android sdk 安装目录下,有一个SDK M ...
- 读取文件时,使用file.eof()判断结尾注意事项
今天写一个小功能需要读取文件,在判断文件结尾时使用了以下语句: while(infile.eof() && infile.good()) { infile.read((); encod ...
- Kafka的安装 -- 未完成
1. 官网下载软件 2. linux服务器上, 安装上传和下载的工具 yum install -y lrzsz rz : 上传 sz + 文件名 : 下载 3.解压文件 pwd: 查看当前路径 解压到 ...
- Server SSL certificate verification failed: certificate has expired, issuer is not trusted
Unable to connect to a repository at URL 'https://xxxxx/svn/include' Server SSL certificate verifica ...