【JUC源码解析】AQS
简介
AQS,也即AbstractQueuedSynchronizer,抽象队列同步器,提供了一个框架,可以依赖它实现阻塞锁和相关同步器。有两种类型,独占式(Exclusive)和共享式(Share)。
概述
同步器,维护了一个共享状态(state)和一个同步队列(链表)。
共享状态,表示共享资源的状态;初始时为0,表示未锁定,当有一个线程成功抢占此资源时,状态加1,释放资源时,状态减1;通过CAS改变state,一般需要子类实现具体的逻辑。
同步队列(链表,Node),当一个线程抢占资源失败时,会为此线程创建一个Node,并添加到链表尾部。链表里只有排在前头的结点对应的线程才有资格竞争资源,成功则获得锁,访问资源结束后,释放锁,结点也从链表中移除,下次来竞争资源时,会重新为其创建结点。

结点
Node
static final class Node {
static final Node SHARED = new Node(); // 标记一个结点(对应的线程)在共享模式下等待
static final Node EXCLUSIVE = null; // 标记一个结点(对应的线程)在独占模式下等待
static final int CANCELLED = 1; // waitStatus的值,表示该结点(对应的线程)已被取消
static final int SIGNAL = -1; // waitStatus的值,表示后继结点(对应的线程)需要被唤醒
static final int CONDITION = -2; // waitStatus的值,表示该结点(对应的线程)在等待某一条件
static final int PROPAGATE = -3; // waitStatus的值,表示有资源可用,新head结点需要继续唤醒后继结点(共享模式下,多线程并发释放资源,而head唤醒其后继结点后,需要把多出来的资源留给后面的结点;设置新的head结点时,会继续唤醒其后继结点)
volatile int waitStatus; // 等待状态,取值范围,-3,-2,-1,0,1
volatile Node prev; // 前驱结点
volatile Node next; // 后继结点
volatile Thread thread; // 结点对应的线程
Node nextWaiter; // 等待队列里下一个等待条件的结点
final boolean isShared() { // 判断是否为共享模式
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException { // 前驱结点
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // 初始化head或share标记结点
}
Node(Thread thread, Node mode) { // 同步队列
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // 等待队列
this.waitStatus = waitStatus;
this.thread = thread;
}
}
属性
private transient volatile Node head; // 指向同步队列(wait queue)的头结点
private transient volatile Node tail; // 指向同步队列(wait queue)的尾节点
private volatile int state; // 同步状态
独占模式
获取资源入口
acquire(int)
public final void acquire(int arg) { // 入口
// 竞争资源成功,直接返回;否则,入队等待,司机而动
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); // 补上中断
}
尝试获取资源
tryAcquire(int)
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException(); // 留给子类实现
}
快速添加结点
addWaiter(Node)
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // 为当前线程创建结点
Node pred = tail;
if (pred != null) { // 队列不为空,尝试快速添加结点
node.prev = pred;
if (compareAndSetTail(pred, node)) { // 设置node为tail结点
pred.next = node; // 老tail结点的后继指向新tail结点
return node; // 返回老tail结点,注意,是老的tail结点
}
}
enq(node); // 否则,自旋添加结点
return node;
}
快速通道,当队列不为空时,将结点添加到队尾,CAS操作,成功,则返回老的末尾结点。如下图所示。如果失败,则进入自旋添加结点通道。

自旋添加结点
enq(Node)
private Node enq(final Node node) {
for (;;) { // 自旋
Node t = tail;
if (t == null) { // 队列为空
if (compareAndSetHead(new Node())) // 创建一个标记结点,head指向它
tail = head; // tail也指向此结点
} else {
node.prev = t; // node的前驱指向tail结点
if (compareAndSetTail(t, node)) { // 设置node为tail结点
t.next = node; // 老tail结点的后继指向新tail结点
return t; // 返回老tail结点,注意,是老的tail结点
}
}
}
}
自旋添加结点,如果队列为空,创建一个标记结点,head和tail都指向它,否则,设置node结点为新的tail结点,CAS操作,失败重试,直至成功。 见下图。
队列为空,添加标记结点,好处是,第一个入队的线程结点和后续结点是一样的逻辑,无需特别处理。

入队等待,伺机而动
acquireQueued(Node, int)
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)) { // 如果前驱是head结点,表示自己可以竞争资源了(等待中被唤醒或中断)
setHead(node); // 成功后,将自己设置为head结点
p.next = null; // 老的head结点出队
failed = false; // 成功
return interrupted; // 返回
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 寻找安全停靠点,检查中断
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // 失败则取消
}
}
入队等待。如果当前结点的前驱是head结点,则尝试获取资源,成功则将此结点设置为head结点,老的head结点出队,返回。否则,寻找安全停靠点,并检查中断标记,如果未找到停靠点或被唤醒,则继续尝试竞争资源。线程总是在安全停靠点处打盹儿(挂起),或是在竞争资源的路上。

寻找安全停靠点
shouldParkAfterFailedAcquire(Node, Node)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 查看前驱的等待状态
if (ws == Node.SIGNAL) // 释放锁后会唤醒自己
return true; // 可以在此停靠(阻塞)
if (ws > 0) { // 如果前驱已取消
do { // 一直往前找
node.prev = pred = pred.prev; // 将自己的前驱往前移
} while (pred.waitStatus > 0); // 直到安全停靠点(没有取消的结点)
pred.next = node; // 并将自己设置为前驱的后继结点
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 与前驱约定好,释放锁后唤醒自己
}
return false; // 暂时不能停靠,继续尝试获取锁
}
寻找安全停靠点。所谓安全停靠点,指的是,当前结点的前驱结点waitStatus = -1(SIGNAL)。否则,检查其是否是有效的,如果无效(已经取消)则往前找,直到找到第一个有效结点,并设置为自己的前驱结点,也把自己设置为它的后继结点(等于把无效结点截掉了);然后将前驱结点的waitStatus设置为-1,并继续竞争资源,暂时不能停靠。如下图。

停靠并检查中断
parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 线程挂起
// 检查中断,并清除中断标记,如果不清除,下次挂起时,会立刻响应中断,所以要清除;最后再把中断补上
return Thread.interrupted();
}
线程挂起,被唤醒后,需要检查中断标记,同时清除中断标记,是为了下次挂起时,被上次的中断标记立刻中断,从而再也无法被挂起(因为一直有中断标记,一旦被挂起,就立刻响应中断)

释放资源入口
release(int)
public final boolean release(int arg) {
if (tryRelease(arg)) { // 成功释放
Node h = head; // head结点
if (h != null && h.waitStatus != 0) // waitStatus等于0,说明后面没有要释放的线程结点
unparkSuccessor(h); // 唤醒等待队列里下一个结点对应的线程
return true; // 成功
}
return false; // 失败
}
独占式释放资源,这里肯定是单线程,无需考虑并发。head结点存在,并且waitStatus不为0(肯定也不为1,取消),则唤醒等待队列里下一个结点对应的线程。
释放资源
tryRelease(int)
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException(); // 留给子类实现
}
通知后继结点
unparkSuccessor(Node)
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // waitStatus置为0
Node s = node.next; // 后继结点
if (s == null || s.waitStatus > 0) { // 如果后继结点不存在,或者已取消
s = null;
// 从tail结点开始,往前搜索,直至第一次遇见取消的结点,并将改取消结点的后继结点作为待唤醒的结点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null) // 如果后继节点存在,则唤醒其所对应的线程
LockSupport.unpark(s.thread);
}
通知后继结点。当前结点的waitStatus设置为0,不成功也没关系,可能被等待线程改变了(寻找安全停靠点的那个家伙);检查当前线程的后继结点,如果不存在(可能取消了),就从tail结点开始往前找,直到第一次遇见取消的结点,然后将这个取消结点的后继结点作为当前线程要唤醒的后继结点;最后,如果找到了要唤醒的结点,则唤醒此结点对应的线程。

共享模式
获取资源入口
acquireShared(int)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 尝试获取资源
doAcquireShared(arg); // 入队等待
}
尝试获取资源
tryAcquireShared(int)
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException(); // 留给子类实现
}
入队等待
doAcquireShared(int)
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) { // 在head后面可以竞争资源
int r = tryAcquireShared(arg); // 尝试获取资源
if (r >= 0) { // 获取成功
setHeadAndPropagate(node, r); // 设置head指向当前结点,如果剩余资源,通知后面的结点
p.next = null; // help GC
if (interrupted) // 补上中断
selfInterrupt();
failed = false; // 成功
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 寻找安全停靠点,检查中断
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // 失败则取消
}
}
共享模式下,入队等待。首先尝试获取资源,会返回一个整数,大于0说明还有资源,后面的线程可以继续抢占。否则,寻找安全停靠点,检查中断;如果找不到停靠点或者被唤醒,则继续竞争资源。
通知后面的结点
setHeadAndPropagate(Node, int)
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node); // 设置head指向当前结点
// 如果还有剩余资源,或者是老head结点为空,或者老head结点的等待状态小于0,或者是新head结点为空,或者新head结点的等待状态小于0
if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
Node s = node.next; // 后继结点
if (s == null || s.isShared()) // 如果后继结点为空,或者后继结点处于共享模式
doReleaseShared(); // 释放
}
}
以下几种情况均需要唤醒后面的线程,包括还剩余资源,或者head结点为空,或者head结点的等待状态小于0;或者新的head结点为空,或者新的head结点的等待状态小于0

释放资源入口
releaseShared(int)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 尝试释放资源
doReleaseShared(); // 唤醒后面的结点对应的线程
return true;
}
return false;
}
共享模式释放资源,也许是多个线程并发释放,所以需要考虑并发问题,同样是CAS操作(CPU指令原语,保证原子性)
唤醒后面的线程
doReleaseShared()
private void doReleaseShared() {
for (;;) { // 自旋,由于是共享模式,存在并发问题,CAS操作
Node h = head; // head结点
if (h != null && h != tail) { // 队列不为空
int ws = h.waitStatus; // 等待状态
if (ws == Node.SIGNAL) { // 如果是signal,表示后继结点线程需要被唤醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 设置waitStatus为0,防止重复
continue;
unparkSuccessor(h); // 唤醒后继结点线程
// 保证传播,设置waitStatus为propagate, 结合setHeadAndPropagate(Node, int), waitStatus < 0时,,调用doReleaseShared()方法
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head) // 有新的结点入队(空队列时),或者有结点出队,导致head结点改变,需要重新释放
break;
}
}
共享模式下,多线程并发释放资源,而head唤醒其后继结点后,需要把多出来的资源留给后面的结点;设置新的head结点时,会继续唤醒其后继结点。

传播是什么鬼
共享模式下,要保证释放事件的传播。比如,一个共享资源,可以同时容纳3个线程同时访问。假如,有两个获得资源的线程访问结束,相继释放锁,而此时,临界区外只有一个线程在等待,且已经创建好结点排在同步队列里,就在head结点的后面。
反过来想,假如没有propagate状态,如下图所示。
1. 线程1释放资源,head结点的等待状态,ws = -1,不变,tail结点的线程被唤醒(还没有成为新的head结点,正在竞争资源的路上),此时,已经没有了等待线程;而这个时候,线程2也释放资源,由于head(还是原来的head)结点的等待状态,ws依然是-1(唤醒后继线程),还需要唤醒后面的线程,然而并没有待唤醒的线程;为了不重复这个动作,我们可以在所有的线程释放资源之前,安全地将head结点的ws设置为0,这样就能保证,只有一个线程能设置成功,因此避免了重复唤醒的问题(因为没有多余的等待线程)。
2.接1,但是又有一个问题,假如,在tail线程正在获取资源时,又有新的线程进来了(入队寻找安全点),准备排在了tail结点后面,恰巧tail结点成为新的head结点时,它的ws还是0,老的head结点的ws也是0,调用setHeadAndPropagate方法时,并不会调用doReleaseShared方法(不满足条件),【注意,一个释放事件可能被忽略】,然后新生结点轮到CPU时间片(已经找到了安全点,准备跟前驱结点协商,醒后通知自己),把前驱结点的ws设置为-1,就park了。
3.接2,此时的状态是,资源里面只有两个线程在跑,老的head和老的tail(也是当前head),而新生结点在等待被唤醒,但是资源同时容纳的线程数是3个,当然,又有结点释放资源后,肯定能唤醒新生结点,然而,这降低了并发性。于是乎,propagate状态应运而生。有了它,便能保证不重复唤醒线程,也不落下唤醒事件,而是将唤醒事件传播下去。
4。接3,在doReleaseShared方法里如何保证新的head结点的ws已经被设置为-1了呢,(也许新生结点轮到CPU时间很少);其实不用保证,即使此刻还没协商成功,也没有关系,ws会设置为-3(propagate),唤醒事件已经保留,且会传播下去。等新结点再去设置前驱结点时,发现已经改变,那么便认为不是安全停靠点,于是继续检查,结果发现前驱结点已经是head结点了,太好了,此时可以去竞争资源了,不用被挂起了。

取消
cancelAcquire(Node)
private void cancelAcquire(Node node) {
if (node == null) // 为空,忽略
return;
node.thread = null; // 取消,不必记录线程
Node pred = node.prev; // 前驱
while (pred.waitStatus > 0) // 一直往前找,知道遇见第一个有效的结点,并设置为自己的前驱
node.prev = pred = pred.prev;
Node predNext = pred.next; // 前驱的后继结点
node.waitStatus = Node.CANCELLED; // 设置waitStatus为1,取消
if (node == tail && compareAndSetTail(node, pred)) { // 如果当前结点是最后一个,直接移除
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 如果前驱不是头结点,而且,前驱的等待状态是SIGNAL或不大于0的情况下设置成SIGNAL,并且线程不为空
if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL
|| (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0) // 当前结点的后继结点不为空,且有效
compareAndSetNext(pred, predNext, next); // 移除当前结点,将其前驱和后继连在一起
} else {
unparkSuccessor(node); // 否则,唤醒后继结点(pred是头结点,或者其他的动态情况,比如进来了新的结点,或者有别的结点也取消了,等等)
}
node.next = node; // 等待被回收
}
}
线程取消,其所对应的结点,waitStatus设为1(CANCEL),并且尝试移除该结点。首先找出其前面的第一个有效的结点作为其前驱结点,并取得前驱结点的后继(可能是它自己,也可能不是);然后,如果此结点是末尾结点,那么直接移除,并设置前驱为新的末尾(tail)结点,否则查看前驱是否是头结点,以及前驱的等待状态还有线程,如果条件满足,则移除该结点.,将该结点的前驱和后继连接在一起;如果条件不满足,调用唤醒后继结点的方法,由于waitStatus已经设置为CANCEL,最后该结点一定会被移除的(无效),最后等待垃圾回收。

条件Condition
等待队列
private transient Node firstWaiter; // 等待队列头结点
private transient Node lastWaiter; // 等待队列尾结点
中断模式
private static final int REINTERRUPT = 1; // 在收到信号之后发生过中断
private static final int THROW_IE = -1; // 在收到信号之前发生过中断
等待
await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // 构造结点,加入到等待队列尾部
int savedState = fullyRelease(node); // 释放锁
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // 判断当前结点是否已经在同步队列里,如果不在,就阻塞当前线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) // 等待过程中如果发生了中断,则退出循环
break;
}
// 被唤醒后,重新竞争锁,并记录中断状态
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters(); // 清理一波已经取消的结点
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode); // 报告中断
}
添加结点到等待队列
addConditionWaiter()
private Node addConditionWaiter() {
Node t = lastWaiter; // 末尾结点
if (t != null && t.waitStatus != Node.CONDITION) { // 尾结点取消,清理一波
unlinkCancelledWaiters();
t = lastWaiter; // 有效末尾结点
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); // 创建结点
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node; // 加入到尾部
return node;
}
清除已经取消的结点
unlinkCancelledWaiters()
private void unlinkCancelledWaiters() {
Node t = firstWaiter; // 头结点
Node trail = null; // 蔓延结点,为了记录有效结点,一直向后蔓延;否则,只依赖firstWaiter,末了,还得从尾到头遍历一遍
while (t != null) {
Node next = t.nextWaiter; // 后继结点
if (t.waitStatus != Node.CONDITION) { // 已经取消
t.nextWaiter = null; // 断开
if (trail == null) // trail为空,说明还没遇到有效结点,继续往后找
firstWaiter = next; // 头节点指向下一个
else
trail.nextWaiter = next; // 否则,已经找到有效结点,trail指向的就是目前为止,最靠后的一个有效结点,所以它指向下一个
if (next == null) // 遍历结束
lastWaiter = trail; // 设置尾结点
} else
trail = t; // trail总是指向后一个有效结点
t = next; // 向后找
}
}

释放
fullyRelease(Node)
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState(); // 获取状态
if (release(savedState)) { // 释放
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed) // 失败,则标记为取消
node.waitStatus = Node.CANCELLED;
}
}
结点是否在同步队列里
isOnSyncQueue(Node)
final boolean isOnSyncQueue(Node node) {
// 等待状态还是CONDITION,或者前驱为空,则说明不在,因为它不可能是head结点(同步队列里,只有head结点可以为null),而是被head唤醒的
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // 如果有后继结点,说明肯定在同步队列里了
return true;
return findNodeFromTail(node); // 如果后继结点为空,需要从尾部开始查找,因为不断地有新结点加入进来,但也在不远处
}
private boolean findNodeFromTail(Node node) {
Node t = tail; // 从尾部开始
for (;;) {
if (t == node) // 找到返回
return true;
if (t == null)
return false;
t = t.prev; // 往前移
}
}
等待过程中是否有中断
checkInterruptWhileWaiting(Node)
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}
中断唤醒线程
transferAfterCancelledWait(Node)
final boolean transferAfterCancelledWait(Node node) { // interrupt
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { // 设置成功,直接入队
enq(node);
return true;
}
while (!isOnSyncQueue(node)) // 如果设置失败,说明已经丢了一个信号,说不定正在入队,自旋一会儿,等入队完成
Thread.yield();
return false;
}
报告中断
reportInterruptAfterWait(int)
private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
信号
signal()
public final void signal() {
if (!isHeldExclusively()) // 非独占式,直接抛异常
throw new IllegalMonitorStateException();
Node first = firstWaiter; // 头节点
if (first != null)
doSignal(first); // 发布信号
}
发布信号
doSignal(Node)
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null) // 判断是否为空
lastWaiter = null;
first.nextWaiter = null; // 断开
} while (!transferForSignal(first) && (first = firstWaiter) != null); // 结点取消,则往后移动一个,继续通知
}
唤醒线程
transferForSignal(Node)
final boolean transferForSignal(Node node) { // Signal
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) // 修改失败,说明已经取消
return false;
Node p = enq(node); // 入队,返回前驱结点
int ws = p.waitStatus; // 前驱结点的等待状态
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) // 已经取消,或者waitStatus设置失败,(不是安全停靠点),则唤醒线程
LockSupport.unpark(node.thread);
return true;
}
行文至此结束。
尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_aqs.html
【JUC源码解析】AQS的更多相关文章
- 【JUC源码解析】ScheduledThreadPoolExecutor
简介 它是一个线程池执行器(ThreadPoolExecutor),在给定的延迟(delay)后执行.在多线程或者对灵活性有要求的环境下,要优于java.util.Timer. 提交的任务在执行之前支 ...
- 【JUC源码解析】SynchronousQueue
简介 SynchronousQueue是一种特殊的阻塞队列,该队列没有容量. [存数据线程]到达队列后,若发现没有[取数据线程]在此等待,则[存数据线程]便入队等待,直到有[取数据线程]来取数据,并释 ...
- 【JUC源码解析】ForkJoinPool
简介 ForkJoin 框架,另一种风格的线程池(相比于ThreadPoolExecutor),采用分治算法,工作密取策略,极大地提高了并行性.对于那种大任务分割小任务的场景(分治)尤其有用. 框架图 ...
- 【JUC源码解析】DelayQueue
简介 基于优先级队列,以过期时间作为排序的基准,剩余时间最少的元素排在队首.只有过期的元素才能出队,在此之前,线程等待. 源码解析 属性 private final transient Reentra ...
- 【JUC源码解析】CyclicBarrier
简介 CyclicBarrier,一个同步器,允许多个线程相互等待,直到达到一个公共屏障点. 概述 CyclicBarrier支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后 ...
- 【JUC源码解析】ConcurrentLinkedQueue
简介 ConcurrentLinkedQueue是一个基于链表结点的无界线程安全队列. 概述 队列顺序,为FIFO(first-in-first-out):队首元素,是当前排队时间最长的:队尾元素,当 ...
- 【JUC源码解析】Exchanger
简介 Exchanger,并发工具类,用于线程间的数据交换. 使用 两个线程,两个缓冲区,一个线程往一个缓冲区里面填数据,另一个线程从另一个缓冲区里面取数据.当填数据的线程将缓冲区填满时,或者取数据的 ...
- 多线程与高并发(三)—— 源码解析 AQS 原理
一.前言 AQS 是一个同步框架,关于同步在操作系统(一)-- 进程同步 中对进程同步做了些概念性的介绍,我们了解到进程(线程同理,本文基于 JVM 讲解,故下文只称线程)同步的工具有很多:Mutex ...
- Jdk1.6 JUC源码解析(12)-ArrayBlockingQueue
功能简介: ArrayBlockingQueue是一种基于数组实现的有界的阻塞队列.队列中的元素遵循先入先出(FIFO)的规则.新元素插入到队列的尾部,从队列头部取出元素. 和普通队列有所不同,该队列 ...
- Jdk1.6 JUC源码解析(6)-locks-AbstractQueuedSynchronizer
功能简介: AbstractQueuedSynchronizer(以下简称AQS)是Java并发包提供的一个同步基础机制,是并发包中实现Lock和其他同步机制(如:Semaphore.CountDow ...
随机推荐
- JPA注解实现联合主键
当表中一个主键不能唯一标识一条记录的时候,就需要使用联合主键了,下面是使用JPA注解实现联合主键的代码 1 首先需要建立一个复合主键类,用来存放需要生产联合主键的属性,该类需要实现序列化. packa ...
- C#中Form的Paint事件响应方法与重载虚方法OnPaint()的区别
Form_Paint()方法是Paint事件的响应方法,OnPaint是可重载的虚方法,OnPaint方法是调用Paint事件的,用哪一个,效果是一样,就看那一个方便了内部是这样实现的: protec ...
- 配置git
https://blog.csdn.net/qq_34446663/article/details/81106018
- iview中position: 'fixed'最顶层z-index
使用iview时候使用<Header :style="{position: 'fixed', width: '100%'}">不是最顶层解决方案 根据样式进行解决在ap ...
- spring boot从redis取缓存发生java.lang.ClassCastException异常
目录树 异常日志信息 错误原因 解决方法 异常日志信息 2018-09-24 15:26:03.406 ERROR 13704 --- [nio-8888-exec-8] o.a.c.c.C.[.[. ...
- 四、MapReduce 基础
是一个并行计算框架(计算的数据源比较广泛-HDFS.RDBMS.NoSQL),Hadoop的 MR模块充分利用了HDFS中所有数据节点(datanode)所在机器的内存.CUP以及少量磁盘完成对大数据 ...
- cmd tab自动补全
- ubuntu系统的软件包管理工具
ubuntu系统的软件包管理工具有两种,一种是离线管理,另一种是在线管理 1.离线管理 dpkg工具可以对本地存放的deb安装包进行安装,卸载,查看状态等. dpkg -i app_name_vers ...
- React Native开发之expo中camera的基本使用
之前做RN项目没调用过本地摄像头,今天下班早,做了一个简单的小demo:主要实现的功能:点击拍照按钮进入拍照界面,点击flip进行前后摄像头转换,点击开始拍照实现拍照功能(没写保存到本地的功能,大家可 ...
- Java敲地鼠代码
package test; import java.awt.EventQueue; import java.awt.event.MouseAdapter; import java.awt.event. ...