更多:Java进阶核心知识集
包含:JVM,JAVA集合,网络,JAVA多线程并发,JAVA基础,Spring原理,微服务,Zookeeper,Kafka,RabbitMQ,Hbase,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存等等
高效学习视频
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
上述代码主要完成了同步状态获取、节点构造、加入同步队列以及在同步队列中自旋等待的相关工作,其主要逻辑是:首先调用子类实现的tryAcquire方法,该方法保证线程安全的获取同步状态,如果同步状态获取失败,则构造独占式同步节点(同一时刻只能有一个线程成功获取同步状态)并通过addWaiter方法将该节点加入到同步队列的尾部,最后调用acquireQueued方法,使得该节点以自旋的方式获取同步状态。如果获取不到则阻塞节点中的线程,而被阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。
下面来首先来看下节点构造和加入同步队列是如何实现的。代码如下:
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
// 尝试快速在尾节点后新增节点 提升算法效率 先将尾节点指向pred
Node pred = tail;
if (pred != null) {
//尾节点不为空 当前线程节点的前驱节点指向尾节点
node.prev = pred;
//并发处理 尾节点有可能已经不是之前的节点 所以需要CAS更新
if (compareAndSetTail(pred, node)) {
//CAS更新成功 当前线程为尾节点 原先尾节点的后续节点就是当前节点
pred.next = node;
return node;
}
}
//第一个入队的节点或者是尾节点后续节点新增失败时进入enq
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//尾节点为空 第一次入队 设置头尾节点一致 同步队列的初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
//所有的线程节点在构造完成第一个节点后 依次加入到同步队列中
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
节点进入同步队列之后,就进入了一个自旋的过程,每个线程节点都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这个自旋过程中并会阻塞节点的线程,代码如下:
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) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
再来看看shouldParkAfterFailedAcquire和parkAndCheckInterrupt是怎么来阻塞当前线程的,代码如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驱节点的状态决定后续节点的行为
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/\*前驱节点为-1 后续节点可以被阻塞
\* This node has already set status asking a release
\* to signal it, so it can safely park.
\*/
return true;
if (ws > 0) {
/\*
\* Predecessor was cancelled. Skip over predecessors and
\* indicate retry.
\*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/\*前驱节点是初始或者共享状态就设置为-1 使后续节点阻塞
\* 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.
\*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
//阻塞线程
LockSupport.park(this);
return Thread.interrupted();
}
节点自旋的过程大致示意图如下,其实就是对图二、图三的补充。
图六 节点自旋获取队列同步状态
整个独占式获取同步状态的流程图大致如下:
图七 独占式获取同步状态
当同步状态获取成功之后,当前线程从acquire方法返回,对于锁这种并发组件而言,就意味着当前线程获取了锁。有获取同步状态的方法,就存在其对应的释放方法,该方法为release,现在来看下这个方法的实现,代码如下:
public final boolean release(int arg) {
if (tryRelease(arg)) {//同步状态释放成功
Node h = head;
if (h != null && h.waitStatus != 0)
//直接释放头节点
unparkSuccessor(h);
return true;
}
return false;
}
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;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
//唤醒后续节点
LockSupport.unpark(s.thread);
}
独占式释放是非常简单而且明确的。
总结下独占式同步状态的获取和释放:在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease方法释放同步状态,然后唤醒头节点的后继节点。
3.2 共享式同步状态的获取和释放
共享式同步状态调用的方法是acquireShared,代码如下:
public final void acquireShared(int arg) {
//获取同步状态的返回值大于等于0时表示可以获取同步状态
//小于0时表示可以获取不到同步状态 需要进入队列等待
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
//和独占式一样的入队操作
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
//自旋
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
//前驱结点为头节点且成功获取同步状态 可退出自旋
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//退出自旋的节点变成首节点
setHead(node);
/\*
\* Try to signal next queued node if:
\* Propagation was indicated by caller,
\* or was recorded (as h.waitStatus either before
\* or after setHead) by a previous operation
\* (note: this uses sign-check of waitStatus because
\* PROPAGATE status may transition to SIGNAL.)
\* and
\* The next node is waiting in shared mode,
\* or we don't know, because it appears null
\*
\* The conservatism in both of these checks may cause
\* unnecessary wake-ups, but only when there are multiple
\* racing acquires/releases, so most need signals now or soon
\* anyway.
\*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
与独占式一样,共享式获取也需要释放同步状态,通过调用releaseShared方法可以释放同步状态,代码如下:
public final boolean releaseShared(int arg) {
//释放同步状态
最后
这份文档从构建一个键值数据库的关键架构入手,不仅带你建立起全局观,还帮你迅速抓住核心主线。除此之外,还会具体讲解数据结构、线程模型、网络框架、持久化、主从同步和切片集群等,帮你搞懂底层原理。相信这对于所有层次的Redis使用者都是一份非常完美的教程了。
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
你的支持,我的动力;祝各位前程似锦,offer不断!!!
an releaseShared(int arg) {
//释放同步状态
最后
这份文档从构建一个键值数据库的关键架构入手,不仅带你建立起全局观,还帮你迅速抓住核心主线。除此之外,还会具体讲解数据结构、线程模型、网络框架、持久化、主从同步和切片集群等,帮你搞懂底层原理。相信这对于所有层次的Redis使用者都是一份非常完美的教程了。
[外链图片转存中…(img-3yvzHmL1-1715636606933)]
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
你的支持,我的动力;祝各位前程似锦,offer不断!!!