目录
1.AbstractOwnableSynchronizer源码分析
2.AQS同步器下的Node源码:
3.AbstractQueuedSynchronizer 独占锁
4.1.1AQS独占锁加锁的过程
4.1.2 acquire()方法:
4.1.3 tryAcquire()方法
4.1.4 addWaiter()方法
4.1.5 enq()方法 自旋方式使node进入队尾
4.1.6 acquireQueued() 方法:节点加入队列后,尝试在等待队列中自旋获取资源
4.1.7 shouldParkAfterFailedAcquire() 线程获取资源失败后,判断是否阻塞线程
4.1.8 parkAndCheckinterrupt()方法
5 独占锁解锁过程
5.1 release()方法
5.2唤醒后继节点
序号 | 名称 | 链接地址 |
1 | 并发编程系列(一)创建线程的三种方式及线程如何完成上下文如何切换 | https://blog.csdn.net/qq_38130094/article/details/103443997 |
2 | 并发编程系列(二)之线程的中断 | https://blog.csdn.net/qq_38130094/article/details/103444171 |
3 | 并发系列(三)线程常用的方法 | https://blog.csdn.net/qq_38130094/article/details/103446126 |
4 | 并发编程系列(四)之Thread类源码分析(一) | https://blog.csdn.net/qq_38130094/article/details/103448160 |
5 | 并发编程系列(五)volatile关键字详解 | https://blog.csdn.net/qq_38130094/article/details/103448564 |
6 | 并发编程系列(六)volatile 之 as-if-serial 指令重排 volatile内存语义 volatile原理 | https://blog.csdn.net/qq_38130094/article/details/103543998 |
7 | 线程系列(七)synchronized使用方式 | https://blog.csdn.net/qq_38130094/article/details/103537663 |
8 | 线程系列(八)synchronized实现原理与应用 | https://blog.csdn.net/qq_38130094/article/details/103537668 |
9 | 并发编程系列(九)ThreadLocala是如何解决共享变量的并发问题及源码分析 | https://blog.csdn.net/qq_38130094/article/details/103665098 |
10 | 并发编程系列(十)AQS同步器独占锁加锁与解锁-源码解读 | https://blog.csdn.net/qq_38130094/article/details/103540315 |
11 | 并发编程系列(十一)AQS同步器共享锁加锁解锁源码解读 | https://blog.csdn.net/qq_38130094/article/details/103646505 |
12 | 并发编程系列(十二)AQS同步器条件锁(Condition)加锁解锁源码解读 | https://blog.csdn.net/qq_38130094/article/details/103679146 |
13 | 发编程系列(十三)ReentrantLock 重入锁 | https://blog.csdn.net/qq_38130094/article/details/103843779 |
14 | 发编程系列(十四)Semaphore信号量 | https://blog.csdn.net/qq_38130094/article/details/103846420 |
15 | 发编程系列(十五) CountDownLatch闭锁 | https://blog.csdn.net/qq_38130094/article/details/103855632 |
16 | 并发编程系列(十六) CyclicBarrier | https://blog.csdn.net/qq_38130094/article/details/103859704 |
线程池的线程特殊是AQS的子类,通过类图可以看到无论是公平锁还是非公平锁都是依赖AbstractQueuedSynchronizer实现的;
AbstractOwnableSynchronizer是AbstractQueuedSynchronizer的父类为创建可能需要所有权概念的锁和相关同步器提供了基础,首先我们膜拜下并发包大神Doug Lea的代码
/**
*可能由线程独占的同步器。这个
*类为创建锁和相关同步器提供了基础
*这可能需要一个所有权的概念。这个
*{@code AbstractOwnableSynchronizer}类本身不管理或
*使用此信息。但是,子类和工具可以使用
*适当维护的值有助于控制和监视访问
*并提供诊断。
*
* @since 1.6
* @author Doug Lea
*/
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/** Use serial ID even though all fields transient. */
private static final long serialVersionUID = 3737899427754241961L;
/**
* 子类使用的空构造函数
*/
protected AbstractOwnableSynchronizer() { }
/**
* 独占模式同步的当前所有者。
*/
private transient Thread exclusiveOwnerThread;
/**
*设置当前拥有独占访问权限的线程。
*{@code null}参数表示没有线程拥有访问权限。
*此方法不强制任何同步或
*{@code volatile}字段访问。
*@param thread所有者线程
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* Returns the thread last set by {@code setExclusiveOwnerThread},
* or {@code null} if never set. This method does not otherwise
* impose any synchronization or {@code volatile} field accesses.
* @return the owner thread
*返回由{@code setexclusioneownerthread}最后设置的线程,
*或者{@code null}如果从未设置。否则,这种方法不会
*强制任何同步或{@code volatile}字段访问。
*@返回所有者线程
*/
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
//AbstractQueuedSynchronizer的内部类
/**
* 等待队列的节点类
* 通过Node可以实现两个队列
* 1通过prev和next实现CLH队列(线程同步队列,双向队列)
* 2nextWaiter实现Condition条件上的等待队列(单项队列)
*/
static final class Node {
/** 标示节点当前在共享模式下 */
static final Node SHARED = new Node();
/** 标示当前节点在独占模式下 */
static final Node EXCLUSIVE = null;
/** 下面的几个int值常量是给waitStatus使用 */
/** 线程已取消*/
static final int CANCELLED = 1;
/** 其表示当前node的后继节点需要被唤醒 */
static final int SIGNAL = -1;
/** 线程等待codition条件*/
static final int CONDITION = -2;
/**
* 共享模式Node有可能处在这种状态,表示锁的下一个获取可以无条件传播
*/
static final int PROPAGATE = -3;
/**
*取值范围只可能是:
* SIGNAL:表示当前node的后继节点对应的线程需要被唤醒
* CANCELLED:此线程已经取消,可能是超时或者中断
* CONDITION:次node当前处于提哦啊见队列中
* PEOPAGATE:当前场景下后续的acquierShared能够得以执行,
* 0 :对于正常的同步节点,该字段初始化为0
*/
volatile int waitStatus;
/**
*当前结点的前驱结点,用于检查waitStatus
* 如当前结点取消,那就需要前驱结点和后继节点来完成连接
*/
volatile Node prev;
/**
* 指向当前结点在释放时唤醒的后继节点
*/
volatile Node next;
/**
* 入队时的当前线程
*/
volatile Thread thread;
/**
* 存储condition队列中的后继节点
*/
Node nextWaiter;
/**
* 如果是在共享模式下等待,返回true
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
*返回当前节点的前驱结点
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // 无参构造
}
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;
}
}
独占锁至少有两个功能:1获取锁的功能当多个线程一起获取锁的时候,只有一个线程能够获取到锁,其他线程必须在当前位置阻塞等待
2释放锁的功能,获取锁的线程释放锁资源,而且必须唤醒正在等待锁资源的一个线程
/**
以独占模式获取,忽略中断。
*通过至少调用一次{@link#tryAcquire},
*回归成功。否则线程将排队,可能
*反复阻塞和解除阻塞,调用{@link
*努力获得成功。这种方法可以使用
*实现方法{@link Lock#Lock}。
*
* @param arg获取的参数。此值传递给
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
代码示意:
(1)调用tryAcquire() 方法,返回true则结束(tryAcquire一般都是在AQS的子类中实现)
(2)如果调用tryAcquire返回的fasle则调用addWaiter() 方法入队
(3)调用acquireQueued方法等待队列中获取资源;
步骤如下:
tryAcquire()在AQS中是空方法,子类要重写该方法才能调用,这里没有定义abstract方法是为了独占锁重写tryAcquire和tryRelease,共享锁重写tryAcquireShared和tryReleaseShared如果定义为abstract所有子类都要重写
tryAcquire是子类要实现的方法;这里不在过多的介绍
/**
* 加入独占线程节点到队尾上
*/
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
//失败,则采用自旋加入结点直至成功返回该节点
Node pred = tail;
//队尾非空
if (pred != null) {
node.prev = pred;
//如果CAS入队尾成功 返回结点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果队尾为空
//或者通过CAS进入队尾失败,存在竞争
//通过end()方法自旋
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*自旋方式使node进入队尾
*/
private Node enq(final Node node) {
//自旋
for (;;) {
//队尾结点
Node t = tail;
//如果队列为空
if (t == null) { // Must initialize
//创建head结点
//CAS设置队列头结点
if (compareAndSetHead(new Node()))
//此时队列只有一个结点
//头结点等于尾结点
tail = head;
} else {
//设置node结点的prev引用指向t
//node.prev = t;
//CAS设置新的队尾结点为node
node.prev = t;
if (compareAndSetTail(t, node)) {
//原队尾结点的next引用指向node
//node就是新的节点tail
t.next = node;
//自旋结束,返回原尾结点
return t;
}
}
}
}
/**
*结点加入队列后,尝试在等待队列中自旋获取资源
*/
final boolean acquireQueued(final Node node, int arg) {
//标记是否拿到资源
boolean failed = true;
try {
//标记是否中断
boolean interrupted = false;
//自旋
for (;;) {
//node的前驱结点,会抛出NullPointerException
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);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获得前驱结点的状态
//根据前驱结点的状态判断是否将线程阻塞
int ws = pred.waitStatus;
//如果前驱结点已经被设置为SIGNAL状态
if (ws == Node.SIGNAL)
/**
*前驱结点释放资源后马上唤醒后继节点
*返回true 表示阻塞线程
*/
return true;
//如果前驱结点的线程被撤销,跳过所有的被撤销的pro结点
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 {
/*
* 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() {
//park()会让当前线程进入waiting状态
//在此状态下,有两种途径可以唤醒该线程
//1:unpark()
//2:interrupt()
LockSupport.park(this);
//返回线程是否被中断,会清除中断标识
return Thread.interrupted();
}
4.1.9 selfInterrupt()方法
中断线程,并且不对中断做出响应
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
总结:accquire方法流程
1:尝试获取资源,如果获取资源成功,tryAcquire()方法返回true,则accquire方法执行结束,否则进入步骤2
2:如果尝试获取资源失败,tryAccquire方法返回false,则执行addWaiter()方法将线程以结点的方式加到队列的末尾,
3:addWaiter方法也许会存在竞争(并发执行的)造成入队为失败,需要通过自旋方式;
4:入队尾成功后,通过accquireQueued方法尝试再队列中获取资源或者阻塞线程,
5:park方法阻塞线程后,等待前驱结点调用unpark方法或者线程中断来唤醒线程
6:判断线程是否中断并且维护线程中断的标识
/**
*释放独占的资源
*会唤醒等待队列中的其他线程来获取资源
*/
public final boolean release(int arg) {
//如果tryRelease释放资源成功
if (tryRelease(arg)) {
//队列中的头结点
Node h = head;
//头结点非空,并且头结点的waitStatus不等于0
if (h != null && h.waitStatus != 0)
//唤醒后继节点,让后继节点竞争资源
unparkSuccessor(h);
//释放独占的资源成功,放回true
return true;
}
//释放独占资源失败,返回false
return false;
}
/**
* 唤醒后继节点
*/
private void unparkSuccessor(Node node) {
/*
*节点的状态
如果小于0,则肯定不是CANCELLED状态
*/
int ws = node.waitStatus;
//cas将节点状态修改为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* 节点node的后继节点
*/
Node s = node.next;
//如果后继节点非空,且状态大于0,即CANCELLED
//说明后继节点的线程取消对资源的等待
if (s == null || s.waitStatus > 0) {
//将后继节点至为空
s = null;
//从队尾结点开始向前遍历
//找到队列中的node节点后第一个等待唤醒的节点
//如果遍历到节点t非空且不等于当前结点node
//则校验节点t的状态
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
unparkSuccessor()方法执行流程
1:将node节点的状态设置为0
2:寻找到下一个非取消状态的节点s
3:如果节点s部位null,则调用LockSupport.unpark(s.thread)方法唤醒s所在的线程(唤醒线程也是有顺序的,就是添加到CLH队列的顺序)
鸟欲高飞先振翅,人求上进先读书-
------------------李苦禅-----------------