Condition 源码学习 ゝ一世哀愁。 2022-12-28 06:12 124阅读 0赞 ## 是什么 ## 任意一个Java对象,都拥有一组监视器方法(Object类上) 1. wait() 2. wait(long timeout) 3. notify() 4. notifyAll() 这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的**监视器方法**,与Lock配合可以实现等待/通知模式。 ## 使用例子 ## 获取一个Condition必须通过Lock的newCondition()方法。只有AQS有Condition的实现类,Lock有获取Condition的方法。并且Condition需要与Lock联合使用 当A线程调用await()方法后,A线程会释放锁并在此等待。 其他线程调用Condition对象的signal()方法,通知A线程后才从await()方法返回,并且在返回前**已经获取了锁。** Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void conditionWait() throws InterruptedException { lock.lock(); try { condition.await(); dosomething(); } finally { lock.unlock(); } } public void conditionSignal() throws InterruptedException { lock.lock(); try { condition.signal(); } finally { lock.unlock(); } } ## 接口结构 ## ![在这里插入图片描述][20201216000502392.png] <table> <thead> <tr> <th>方法名</th> <th>作用</th> </tr> </thead> <tbody> <tr> <td>void await()</td> <td>使当前线程等待,直到收到信号或被中断interrupted,关联的锁被<strong>原子</strong>释放,并且当前线程出于线程调度目的而被禁用,并且处于休眠状态直到:<strong>1</strong>.其他线程调用<strong>signal</strong>方法,<strong>并且当前线程恰好被选择为要唤醒的线程</strong>;<strong>2</strong>.其他一些线程调用signalAll;<strong>3</strong>.某些其他线程interrupts当前线程;<strong>4</strong>.发生虚假唤醒。在此方法可以返回之前,当前线程必须重新获取与此条件关联的锁</td> </tr> <tr> <td>void awaitUninterruptibly();</td> <td>相比await(),线程不可被中断</td> </tr> <tr> <td>long awaitNanos(long nanosTimeout)</td> <td>使当前线程等待,直到发出信号或中断它,或者经过指定的等待时间, 相对await()多了超时唤醒,返回值为剩余时间</td> </tr> <tr> <td>boolean await(long time, TimeUnit unit)</td> <td>使当前线程等待,直到发出信号或中断它,或者经过指定的等待时间</td> </tr> <tr> <td>boolean awaitUntil(Date deadline)</td> <td>当前线程进入等待状态,直到被通知或中断,或者经过指定的时间,被通知返回true,到指定时间返回false</td> </tr> <tr> <td>void signal();</td> <td>唤醒一个等待线程。 该线程必须重新获取锁,然后才能从await返回。</td> </tr> <tr> <td>void signalAll();</td> <td>唤醒所有等待的线程。 每个线程必须重新获取锁,然后才能从await返回。</td> </tr> </tbody> </table> ## 两个名词 ## 1. 同步队列 > 存放在竞争锁时暂时没获取到锁的线程的队列,等待前面的节点释放锁后唤醒 1. 等待队列 > 存放 调用Condition.await方法,线程进入等待状态,需要等其他线程调用Condition.signal的线程 ## 实现类ConditionObject ## ### 属性 ### Condition拥有首节点(firstWaiter)和尾节点(lastWaiter),有头节点就能构建一个等待队列FIFO。 * private transient Node firstWaiter; > 等待队列的第一个节点。 * private transient Node lastWaiter; > 等待队列的最后一个节点。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzZGZ5bQ_size_16_color_FFFFFF_t_70] ## void await() ## **实现可中断条件等待** 1. 构建节点,加入到等待队列尾部 2. 释放锁,唤醒同步队列中的后继节点 3. 循环判断,节点是否已经在同步队列,不在的话线程挂起 4. 等待线程被唤醒,再判断线程是否在同步队列 5. 在的同步队列的话,参与竞争锁 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); } 几个注意点: 1. 为什么没看到CAS操作:因为发生的一切都是在获取到锁的情况下发生的,依靠锁实现同步状态 2. 线程是在什么时候被移动在同步队列的:当其他线程调用signal()方法,并且等待队列中被选中的是该线程 ## void signal() ## 1. 先将等待队列中的**头节点**移出,修改相应引用 2. 将节点移动到同步队列中 3. 判断同步队列的原尾节点状态为Cancel,或者修改状态为Signal失败时,唤醒该线程投入锁的竞争。 4. 否则等待同步队列前驱节点释放锁后唤醒线程。 public final void signal() { //检测线程是否拥有锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); // 头节点 Node first = firstWaiter; if (first != null) //唤醒头节点 doSignal(first); } //唤醒线程以及一些引用修改 private void doSignal(Node first) { do { //将头指针后移 if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; //节点被移出队列,不需要后继引用 first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); } //将节点从条件队列转移到同步队列。如果成功,则返回true final boolean transferForSignal(Node node) { //将该节点从状态CONDITION改变为初始状态0 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; //将节点加入到同步队列中去,返回同步队列原尾节点 Node p = enq(node); int ws = p.waitStatus; //检查前驱节点的状态,若状态为cancel,尝试唤醒节点 //前驱节点不为取消转态,尝试CAS状态为SIGNAL,如果失败,尝试唤醒节点投入到锁的竞争中去 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; } 线程在调用signal后就会将等待队列的头节点移动到同步队列。等待同步队列的前驱节点唤醒线程。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzZGZ5bQ_size_16_color_FFFFFF_t_70 1] signalAll就算将所有节点都移动到同步队列中。 ## 总结 ## 1. 一个线程获取锁后,通过调用Condition的await()方法,会将当前线程先加入到等待队列中,然后释放锁。 2. 将线程挂起,等待其他线程调用signal()将线程移动到同步队列中 3. 当线程被唤醒后,尝试竞争锁,竞争到锁后会从await方法进行返回。 [20201216000502392.png]: /images/20221120/59dd5cb9c4fa4bffb2119d402fd6b01c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzZGZ5bQ_size_16_color_FFFFFF_t_70]: /images/20221120/638481d5736f40cd90a500a141a064bd.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzZGZ5bQ_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/20201227215252185.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzZGZ5bQ==,size_16,color_FFFFFF,t_70
还没有评论,来说两句吧...