【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. ... 
