写在最后
很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。
最后祝愿各位身体健康,顺利拿到心仪的offer!
由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里
tryAcquireShared()
:共享方式。尝试获取资源。负数表示失败;0
表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。tryReleaseShared()
:共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true
,否则返回false
。isHeldExclusively()
:该线程是否正在独占资源。只有用到condition
才需要去实现它。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现 tryAcquire-tryRelease
、tryAcquireShared-tryReleaseShared
中的一种即可。但 AQS
也支持自定义同步器同时实现独占和共享两种方式,如 ReentrantReadWriteLock
。
2. AQS 源码解析
2.1 存储节点 Node
我们一直在说 AQS
是基于 FIFO
队列的存储结构,它是以内部类 Node
节点的形式进行存储。这个等待队列是 CLH
同步队列。
static final class Node {
/** 共享节点模式下的节点 /
static final Node SHARED = new Node();
/* 独占模式下的节点 */
static final Node EXCLUSIVE = null;
/** 取消状态 /
static final int CANCELLED = 1;
/* 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行 /
static final int SIGNAL = -1;
/* waitStatus value to indicate thread is waiting on condition /
static final int CONDITION = -2;
/*
- 下一次共享式同步状态获取将会无条件地传播下去
*/
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;
/**
- 获取同步状态的线程
*/
volatile Thread thread;
/**
- Link to next node waiting on condition, or the special
- value SHARED. Because condition queues are accessed only
- when holding in exclusive mode, we just need a simple
- linked queue to hold nodes while they are waiting on
- conditions. They are then transferred to the queue to
- re-acquire. And because conditions can only be exclusive,
- we save a field by using special value to indicate shared
- mode.
*/
Node nextWaiter;
/**
- Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
- Returns previous node, or throws NullPointerException if null.
- Use when predecessor cannot be null. The null check could
- be elided, but is present to help the VM.
- @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
在 Node
内部类中,声明了 pre
、next
节点用于队列的连接,同时保存了 waitStatus
状态。
2.2 AQS 类解析
在面向对象的世界中,想要了解一个类有什么特点,就要看它的属性。通过查看源码,我们看到 AQS
类包含:
/**
- Head of the wait queue, lazily initialized. Except for
- initialization, it is modified only via method setHead. Note:
- If head exists, its waitStatus is guaranteed not to be
- CANCELLED.
*/
private transient volatile Node head;
/**
- Tail of the wait queue, lazily initialized. Modified only via
- method enq to add new wait node.
*/
private transient volatile Node tail;
/**
-
The synchronization state.
*/
private volatile int state; -
前驱头节点 -
head
-
后驱尾节点 -
tail
-
同步器状态 -
state
AQS
基于 FIFO
队列,接下来就依照 acquire-release
、acquireShared-releaseShared
的次序来分析入队和出队。
2.2.1. acquire(int)
此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源成功,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。这也正是 lock()
的语义,当然不仅仅只限于 lock()
。获取到资源后,线程就可以去执行其临界区代码了。下面是 acquire
源码:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在 acquire
方法中调用了 tryAcquire()
、acquireQueued()
、addWaiter()
三个方法。
首先通过tryAcquire
方法尝试申请独占锁。如果获取成功则返回 true
,否则返回 false
。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
源码中直接 throw
一个异常。结合我们前面自定义锁的知识,AQS
只是一个框架,具体资源获取和释放方式交由自定义同步器实现。AQS
这里只定义了一个接口,具体资源的获取交由自定义同步器去实现了(通过 state
的 get/set/CAS
)!!!至于能不能重入,能不能加塞,那就看具体的自定义同步器怎么去设计了!!!当然,自定义同步器在进行资源访问时要考虑线程安全的影响。
这里之所以没有定义成 abstract
,是因为独占模式下只用实现 tryAcquire-tryRelease
,而共享模式下只用实现 tryAcquireShared-tryReleaseShared
。如果都定义成 abstract
,那么每个模式也要去实现另一模式下的接口。说到底,Doug Lea
还是站在咱们开发者的角度,尽量减少不必要的工作量。
1. addWaiter()
接着就是 addWaiter()
方法用于将当前线程添加到等待队列的队尾,并返回当前线程所在的节点。
private Node addWaiter(Node mode) {
//以给定的Node节点模式构建当前线程的Node节点,在acquire方法中传入的是EXCLUSIVE独占式节点
Node node = new Node(Thread.currentThread(), mode);
// 将尾节点进行保存
Node pred = tail;
if (pred != null) {//如果尾节点不为null
//将尾节点设置尾新节点的prev节点
node.prev = pred;
if (compareAndSetTail(pred, node)) {//通过CAS保证,确保节点能够被线程安全的添加
//将当前界定指向前驱的next节点
pred.next = node;
return node;
}
}
//如果尾节点为null,则通过enq进行入队
enq(node);
return node;
}
//同步器通过死循环的方式来保证节点的正确添加,在“死循环” 中通过CAS将节点设置成为尾节点之后,
//当前线程才能从该方法中返回,否则当前线程不断的尝试设置。
private Node enq(final Node node) {
//CAS"自旋",直到成功加入队尾
for (;😉 {
//尾节点临时存储
Node t = tail;
if (t == null) { // Must initialize
//如果tail为null,则将
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在 addWaiter(Node node)
方法中,将当前线程节点添加到等待队列中。
2. acquireQueued()
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
- acquireQueued方法当前线程在死循环中获取同步状态,而只有前驱节点是头节点才能尝试获取同步状态(锁)( p == head && tryAcquire(arg))
-
原因是:1.头结点是成功获取同步状态(锁)的节点,而头节点的线程释放了同步状态以后,将会唤醒其后继节点,后继节点的线程被唤醒后要检查自己的前驱节点是否为头结点。
-
2.维护同步队列的FIFO原则,节点进入同步队列之后,就进入了一个自旋的过程,每个节点(或者说是每个线程)都在自省的观察。
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环检查(自旋检查)当前节点的前驱节点是否为头结点,才能获取锁
for (;😉 {
// 获取节点的前驱节点
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {//节点中的线程循环的检查,自己的前驱节点是否为头节点
//将当前节点设置为头结点,移除之前的头节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 否则检查前一个节点的状态,看当前获取锁失败的线程是否要挂起
if (shouldParkAfterFailedAcquire(p, node) &&
//如果需要挂起,借助JUC包下面的LockSupport类的静态方法park挂起当前线程,直到被唤醒
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//如果有异常
if (failed)
//取消请求,将当前节点从队列中移除
cancelAcquire(node);
}
最后
Java架构进阶面试及知识点文档笔记
这份文档共498页,其中包括Java集合,并发编程,JVM,Dubbo,Redis,Spring全家桶,MySQL,Kafka等面试解析及知识点整理
Java分布式高级面试问题解析文档
其中都是包括分布式的面试问题解析,内容有分布式消息队列,Redis缓存,分库分表,微服务架构,分布式高可用,读写分离等等!
互联网Java程序员面试必备问题解析及文档学习笔记
Java架构进阶视频解析合集
852)]
互联网Java程序员面试必备问题解析及文档学习笔记
[外链图片转存中…(img-0DwinRSg-1715629814852)]
Java架构进阶视频解析合集