condition源码分析
目录:
参考博客:
https://www.cnblogs.com/gemine/p/9039012.html
https://www.jianshu.com/p/037c2bd99440
https://zhuanlan.zhihu.com/p/89703576
1:注意事项
condition是ReentrantLock中的对象,使用condition必须配合lock锁一起使用,否则会报错,原因以下会分析
2:创建方式
ReentrantLock lock = new ReentrantLock();
Condition fullCondition =lock.newCondition();
3:使用案例
package com.saytoyou.com.thread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionTest { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(new Runnable() { @Override public void run() { lock.lock(); System.out.println("你好,我抢到了资源开始执行"); try { condition.await(); System.out.println("我又获取到资源了"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } }).start(); new Thread(new Runnable() { @Override public void run() { lock.lock(); System.out.println("你好,我是线程signal"); try { condition.signal(); System.out.println("开始释放资源"); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } }).start(); } }
说明:需要注意的是,signal必须是在await方法之前执行,不然会导致死锁,好了接下来进行源码分析
4:源码分析
4.1await源码如下
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter(); 加入等待队列 int savedState = fullyRelease(node); 释放当前线程占用的锁资源,因为线程不知道state是几,所以释放完成,返回state的值,保存起来 int interruptMode = 0; while (!isOnSyncQueue(node)) { 循环判断是否在同步队列中,在同步队列中,说明signal调用了,可以进行如下操作,否否则等待 LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 检查是否被中断过,返回中断的状态,同时加入等待队列 break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 利用AQS放入阻塞队列,尝试获取资源,设置state值再利用 interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); 配合checkInterruptWhileWaiting方法返回值判断是要抛出异常还是自我中断
}
whille()循环详解,刚开始进来肯定不在同步对列中,所以false进入循环,park,等待唤醒,唤醒之后不管下面if循环是否有效,再次进入while循环,这时候
肯定已经在同步队列了,所以会跳出循环,进行接下来的操作
注意最后一步checkInterruptWhileWaiting方法分析如下,判断是否需要延续中断,最后会有解释
结合上面判断中断状态就可以理解了,如果是在signal之前发生的中断,则需要补充一次中断,否则抛出异常,因为不是通过signal唤醒的,而是中断唤醒的
4.2:上述addConditionWaiter源码如下
private Node addConditionWaiter() { Node t = lastWaiter; // If lastWaiter is cancelled, clean out. if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters(); 清除队列中不是condition状态的节点 t = lastWaiter; } Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node; }
新建一个node节点,赋值为-2也就是condition状态,然后加入到队列尾部,同时把最后一个lastWaiter指向新节点
unlinkCancelledWaiters,方法就是把队列中不是condition状态的节点清楚掉
4.3:unlinkCancelledWaiters方法如下
private void unlinkCancelledWaiters() { Node t = firstWaiter; Node trail = null; while (t != null) { Node next = t.nextWaiter; if (t.waitStatus != Node.CONDITION) { t.nextWaiter = null; 帮助gc if (trail == null) firstWaiter = next; else trail.nextWaiter = next; 如果发现状态不正确,就把上个节点剔除,保存当前节点 if (next == null) lastWaiter = trail; } else trail = t; 存放上一个节点 t = next; 存放当前节点,循环往上 } }
大概就是trail保存上一个节点,t保存当前节点,循环往下,如果t节点的下个节点状态不正确,就把t剔除,直接用下一个节点关联到上一个节点
4.4:fullyRelease源码如下:
回到addwaiter方法中
fullyRelease(node);该方法的主要作用就是释放调用await方法的线程的锁资源,同时临时返回state的值,供后面使用
final int fullyRelease(Node node) { boolean failed = true; try { int savedState = getState(); if (release(savedState)) { 在AQS中已经知道,释放资源 failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; } }
4.5:checkInterruptWhileWaiting源码分析如下:
// 退出await的时候重新设置中断 private static final int REINTERRUPT = 1; // await退出的时候需要抛出异常 private static final int THROW_IE = -1; 0 说明await期间没有发生中断 // 判断是否在线程挂起期间发生了中断,如果发生了中断,是 signal 调用之前中断的:抛出异常;还是 signal 之后发生的中断:重新设置中断 private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; } // 线程处于中断状态,才会调用此方法 final boolean transferAfterCancelledWait(Node node) { // 如果此时signal已经发生,NODE状态就不再是CONDITION,所以CAS成功的话,说明中断是在signal前发生的 if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { // 即使signal还没发生,此时是中断引起的unpark,依然会由于条件队列迁移到阻塞队列尾部 enq(node); return true; } // 执行到这一步,说明上边CAS失败,说明waitstatus已不为CONDITION,即signal已经发生后才发生的中断 // signal 方法会将节点转移到阻塞队列,但是可能还没完成,这边自旋等待其完成 while (!isOnSyncQueue(node)) Thread.yield(); return false; }
5:signal方法分析
private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) 如果下一个队列为空,说明最后一个,清空队列 lastWaiter = null; 清空队列 first.nextWaiter = null; 帮助gc } while (!transferForSignal(first) && (first = firstWaiter) != null); } final boolean transferForSignal(Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) 如果状态不是condition,则进行下一个,这一个节点直接丢弃 return false; Node p = enq(node); 存放阻塞队列 int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) 更新当前节点状态为signal LockSupport.unpark(node.thread); 唤醒当前节点 ,注意一般代码走不到这里,真正的唤醒,还是最后的release方法,所以在finally里面必须调用release进行释放 return true; }
结合上面判断中断状态就可以理解了,如果是在signal之前发生的中断,则需要补充一次中断,否则抛出异常,因为不是通过signal唤醒的,而是中断唤醒的