AQS源码分析之ConditionObject
来源:互联网 发布:centos 7 伪静态 编辑:程序博客网 时间:2024/06/05 20:22
简介
传统的内置锁都只能有一个相关联的条件队列,因此多个线程可能在同一个条件队列上等待不同的条件谓词,导致使用notify时导致信号消失,或者使用notifyAll唤醒了非等待该信号类型的线程,造成了极大的开销。
因此,可以使用显式的Lock和Condition而不是内置锁和条件队列,来编写一个带有多个条件谓词的并发对象。在每个锁上可存在多个等待、条件等待可以是可中断的或不可中断的、基于限时的等待、以及公平的或非公平的队列操作。
与内置条件队列不同的是,对于每个Lock,可以由任意数量的Condition对象。Condition对象继承了相关的Lock对象的公平性,对于公平的锁,线程会按照FIFO顺序从Condition.await中释放。
public class ConditionBoundedBuffer<T> { protected final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); @SuppressWarnings("unchecked") private final T[] items = (T[]) new Object[19]; private int tail, head, count; public void put(T x) throws InterruptedException { lock.lock(); try { while (count == items.length) { notFull.await(); } items[tail] = x; if (++tail == items.length) { tail = 0; } ++count; notEmpty.signal(); } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lock(); try { while (count == 0) { notEmpty.await(); } T x = items[head]; items[head] = null; if (++head == items.length) { head = 0; } notFull.signal(); return x; } finally { lock.unlock(); } }}
通过将两个条件谓词分开并放到两个等待线程集中,Condition使其更容易满足单次通知的需求,signal比signalAll更高效,能够极大减少在每次缓存操作中发生的上下文切换与锁请求的次数。
与内置锁和条件队列一样,但使用显式的Lock和Condition时,也必须满足锁、条件谓词和条件变量之间的三元关系。在条件谓词中包含的变量必须由Lock来保护,并且在检查条件谓词以及调用await和signal时,必须持有Lock对象。
Condition接口
public interface Condition { /** * 暂停此线程直至一下四种情况发生 * 1.此Condition被signal() * 2.此Condition被signalAll() * 3.Thread.interrupt() * 4.伪wakeup * 以上情况.在能恢复方法执行时,当前线程必须要能获得锁 */ void await() throws InterruptedException; //跟上面类似,不过不响应中断 void awaitUninterruptibly(); //带超时时间的await(),并响应中断 long awaitNanos(long nanosTimeout) throws InterruptedException; //带超时时间的await() boolean await(long time, TimeUnit unit) throws InterruptedException; //带deadline的await() boolean awaitUntil(Date deadline) throws InterruptedException; //唤醒某个等待在此condition的线程 void signal(); //唤醒有等待此condition的所有线程 void signalAll();}
AQS的ConditionObject
ConditionObject的实现其实就是维护了两个队列
- condition队列:表示等待条件队列,其
waitStatus=Node.CONDITION
,由firstWaiter
和lastWaiter
两个舒心控制。 - sync队列:表示可以竞争锁的队列,这个跟AQS的独占锁队列一致,与ReentrantLock独占锁共用一个队列。
await()方法分析
该方法就是把当前线程创建一个Node加入condition队列,释放当前占有的锁之后,接着就一直循环,判断当前节点在不在sync队列队列中,如果不在,则当前线程挂起;否则,就可以重新竞争独占锁。
public final void await() throws InterruptedException { /*如果当前线程已经被中断*/ if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); /*释放当前线程持有的锁*/ int savedState = fullyRelease(node); int interruptMode = 0; /*通过循环,查看当前线程是否已经移入到sync队列中*/ while (!isOnSyncQueue(node)) { /*如果没有在sync队列,那么将当前线程挂起*/ LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } /*当条件满足后,当前线程被唤醒 * 现在重新获取上一步中释放的锁 * */ if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode);}
添加一个等待Node到条件队列中
private Node addConditionWaiter() { Node t = lastWaiter; // If lastWaiter is cancelled, clean out. /*如果最后一个等待节点的等待状态不是 CONDITION * 说明已经被取消了, * 那么则清理掉 * */ if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; } /*新建一个Condition状态的节点,并将其加在尾部*/ Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node;}
释放当前的state,最终还是调用tryRelease方法
final int fullyRelease(Node node) { boolean failed = true; try { /*获取当前的状态值,通过release方法释放锁*/ int savedState = getState(); if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; }}
判断是否在同步队列中
final boolean isOnSyncQueue(Node node) { /*如果状态为CONDITION,说明还在condition队列中,还得循环等待*/ if (node.waitStatus == Node.CONDITION || node.prev == null) return false; /*当执行signal后,会执行enq方法,将node加入到队列中去*/ /*有前驱节点也有后置节点,那么一定在sync队列中*/ if (node.next != null) return true; /* * 其前置节点为非null,但是也不在Sync也是可能的, * 因为CAS将其加入队列失败.所以我们需要从尾部开始遍历确保其在队列 */ return findNodeFromTail(node);}
从尾部查找node节点
private boolean findNodeFromTail(Node node) { Node t = tail; for (; ; ) { if (t == node) return true; if (t == null) return false; t = t.prev; }}
signal()方法分析
该方法就是把条件队列中头结点移动到sync队列中
public final void signal() { /*如果不是独占锁模式,则抛出 非法监视器异常*/ if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //将condition队列的第一个节点出队列,并将其加入AQS的锁sync队列 Node first = firstWaiter; if (first != null) doSignal(first);}
释放信号
private void doSignal(Node first) { do { /*将firstWaiter指向下一个节点 * 如果下一个节点为空,则将lastWaiter置为空 * */ if ((firstWaiter = first.nextWaiter) == null) lastWaiter = null; /*断开头结点与其他节点的连接*/ first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null);}
将一个节点从condition队列转换到Sync队列
final boolean transferForSignal(Node node) { /* * 所谓的condition队列和sync队列就在于waitStatus的值 * 在这里将waitStatus改为NORMAL,如果不能改变,说明当前节点已经被取消(CANCELLED), * 那么重新尝试下一个节点 */ if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; /* * 将当前节点移动到sync队列中,接着设置前驱节点等待状态, * 如果被取消或者设置状态失败,那么唤醒该线程,让该线程重新同步,为了确保waitStatus是无害的 */ Node p = enq(node);/*当前节点的前驱节点*/ int ws = p.waitStatus; /*如果前驱节点取消,或修改状态失败*/ if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) /*唤醒当前线程*/ LockSupport.unpark(node.thread); return true;}
signalAll()方法分析
唤醒所有等待此condition的所有线程
public final void signalAll() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignalAll(first);}
遍历所有节点,使其加入到Sync队列
private void doSignalAll(Node first) { lastWaiter = firstWaiter = null; do { Node next = first.nextWaiter; first.nextWaiter = null; transferForSignal(first); first = next; } while (first != null);}
- AQS源码分析之ConditionObject
- jdk1.8 J.U.C并发源码阅读------AQS之conditionObject内部类分析
- AQS源码分析
- Java AQS源码分析
- AQS(AbstractQueuedSynchronizer)源码分析
- AQS源码分析
- AQS源码分析
- AQS源码分析
- AQS源码分析
- AQS源码分析
- JAVA并发编程学习笔记之AQS源码分析
- AQS源码分析之独占锁和共享锁
- JUC - AbstractQueuedSynchronizer(AQS) 源码分析
- AQS源码分析(获取与释放)
- JUC源码分析8-locks-AQS-condition
- 根据AQS推测CountDownLatch及源码分析
- 根据AQS推测Semaphore及源码分析
- 我之见--java多线程之可重入锁,读写锁源码分析 及自定义锁AQS
- Surfaceview的绘制与应用
- APP被强杀,怎么办?
- 调通sina33m下的OV5640(分色排版)
- 【Linux】中shell脚本语法篇之条件语句和循环语句
- 资源管理器已停止运行,屏幕已黑,explorer.exe打不开
- AQS源码分析之ConditionObject
- 多核编程 与 单核多线程编程的区别
- 一篇就够了系列之Handler全解析
- 【C】从键盘上获取一个字符串,数字升序输出
- sed 替换文件中匹配行头的一行
- RocketMQ原理解析-producer 3.如何发送顺序消息
- Vim使用笔记
- Eclipse中Svn插件和桌面客户端版Svn(TortoiseSVN)版本要对应
- Java中AES加密算法使用