java condition_Java并发编程之 Condition 源码分析

私信我或关注微信号:猿来如此呀,回复:学习,获取免费学习资源包。

ReentrantLock#newCondition() 可以创建 Condition,在 ReentrantLock 加锁过程中可以利用 Condition 阻塞当前线程并临时释放锁,待另外线程获取到锁并在逻辑后通知阻塞线程"激活"。Condition 常用在基于异步通信的同步机制实现中。

83c35f36b42f7ec89bfc60efaf1be573.png

常用方法

Condition 中主要的方法有 2 个:

  • await() 方法可以阻塞当前线程,并释放锁。
  • 在获取锁后可以调用 signal() 通知被 await() 阻塞的线程"激活"。

这里的 await(),signal() 必须在 ReentrantLock#lock() 和 ReentrantLock#unlock() 之间调用。

Condition 实现分析

Condition 的实现也是利用 AbstractQueuedSynchronizer 队列来实现,await() 在被调用后先将当前线程加入到等待队列中,然后释放锁,最后阻塞当前线程。signal() 在被调用后会先获取等待队列中第一个节点,并将这个节点转化成 ReentrantLock 中的节点并加入到同步阻塞队列的结尾,这样此节点的上个节点线程释放锁后会激活此节点线程取来获取锁。

await() 方法源码分析

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) // clean up if cancelled unlinkCancelledWaiters(); //如果当前线程被中断,处理中断逻辑 if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }

主要分以下几步:

  • 先判断是否当前线程是否被中断中断则抛出中断异常如果未中断调用 addConditionWaiter() 加入等待队列。
  • 调用 fullyRelease(node) 释放锁使同步阻塞队列的下个节点线程能获取锁。
  • 调用 isOnSyncQueue(node) 判断是否在同步阻塞队列,这里的加入同步阻塞队列操作是在另一个线程调用 signal() 后加入,如果不在同步阻塞队列会进行阻塞直到被激活。
  • 如果被激活然后调用 checkInterruptWhileWaiting(node) 判断是否被中断并获取中断模式。
  • 继续调用 isOnSyncQueue(node) 判断是否在同步阻塞队列。
  • 是则调用 acquireQueued(node, savedState) 获取锁,这里如果获取不到也会被阻塞,获取不到原因是在第一次调用 isOnSyncQueue(node) 前,可能另一个线程已经调用 signal() 后加入到同步阻塞队列,然后调用 acquireQueued(node, savedState) 获取不到锁并阻塞。acquireQueued(node, savedState) 也会返回当前线程是否被中断,如果被中断设置中断模式。
  • 在激活后调用 unlinkCancelledWaiters() 清理等待队列的已经被激活的节点。
  • 最后判断当前线程是否被中断,如果被中断则对中断线程做处理。

下面来看下 addConditionWaiter() 实现:

 private Node addConditionWaiter() { //获取等待队列尾部节点 Node t = lastWaiter; //如果尾部状态不为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; }

addConditionWaiter() 的逻辑很简单,就是创建以当前线程为基础的节点并把节点加入等待队列的尾部待其他线程处理。下面来看下 fullyRelease(Node 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; } }

调用 getState() 先获取阻塞队列中当前线程节点的锁状态值,这个值可能大于 1 表示多次重入,然后调用 release(savedState) 释放所有锁,如果释放成功返回锁状态值。

2840f9fa6dea5ffca18c8f18ce178c18.png

下面来看下 isOnSyncQueue(Node node) 实现:

 final boolean isOnSyncQueue(Node node) { //判断当前节点是否是CONDITION或者前置节点是否为空如果为空直接返回false if (node.waitStatus == Node.CONDITION || node.prev == null) return false; //如果下个节点存在,则在同步阻塞队列中返回true if (node.next != null) // If has successor, it must be on queue 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; } }

此方法的功能是查找当前节点是否在同步阻塞队列中,方法先是快速判断,判断不了再进行遍历查找。

  • 第一步先判断次节点是否 CONDITION 状态或者前置节点是否存在,如果是表明不在队列中返回 false,阻塞队列中的状态一般是 0 或者 SIGNAL 状态而且如果当前如果当前节点在队列阻塞中且未被激活前置节点一定不为空。
  • 第二步判断节点的下个节点是否存在,如果存在则表明当前当前节点已加入到阻塞队列中。
  • 如果以上2点都没法判断,也有可能刚刚加入到同步阻塞队列中,所以调用 findNodeFromTail(Node node) 做最后的遍历查找。查找从队列尾部开始查,从尾部开始查的原因是可能刚刚加入到同步阻塞队列中,从尾部能快速定位。

下面看下 checkInterruptWhileWaiting(Node node) 实现:

 private int checkInterruptWhileWaiting(Node node) { return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; } final boolean transferAfterCancelledWait(Node node) { if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { enq(node); return true; } while (!isOnSyncQueue(node)) Thread.yield(); return false; }

此方法在线程被激活后被调用,主要功能就是判断被激活的线程是否被中断。此方法会返回 2 种中断状态 THROW_IE 和 REINTERRUPT,THROW_IE 是调用 signal() 前被中断返回,REINTERRUPT 在调用 signal() 后被中断返回。 此方法先判断是否被标记中断,是的话再调用 transferAfterCancelledWait(node) 取判断是那种中断状态,transferAfterCancelledWait(node)方法分 2 步:

  • 用 CAS 方式将节点状态改错等待状态改成 CONDITION,并加入到同步阻塞队列中返回 true。
  • 如果不能加入到同步阻塞队列就自旋一直等待加入。

如果使用 await() 方法,上面 2 步其实是没什么作用其最后一定会返回 false,因为 await() 被激活只能调用 signal() 方法,而 signal() 方法肯定已经将节点加入到同步阻塞队列中。所以以上逻辑是给 await(long time, TimeUnit unit) 等带超时激活方法用的。

acquireQueued(node, savedState) 方法再上一章节已经讲过这边就不重复了,下面分析下 unlinkCancelledWaiters() 方法:

 private void unlinkCancelledWaiters() { //获取等待队列头节点 Node t = firstWaiter; Node trail = null; while (t != null) { //获取下个节点 Node next = t.nextWaiter; //如果状态不为CONDITION说明已经加入阻塞队列需要清理掉 if (t.waitStatus != Node.CONDITION) { t.nextWaiter = null; if (trail == null) firstWaiter = next; else //获取下个节点 trail.nextWaiter = next; if (next == null) lastWaiter = trail; } else trail = t; t = next; } }

此方法就是从头开始查找状态不为 CONDITION 的节点并清理,状态不为 CONDITION 节点说明此节点已经加入到阻塞队列,已经不需要维护。

下面来看下 reportInterruptAfterWait(interruptMode) 方法:

private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { //如果是THROW_IE模式直接抛出异常 if (interruptMode == THROW_IE) throw new InterruptedException(); //如果是REINTERRUPT模式标记线程中断由上层处理中断 else if (interruptMode == REINTERRUPT) selfInterrupt();}

此方法处理中断逻辑。如果是 THROW_IE 模式直接抛出异常,如果是 REINTERRUPT 模式标记线程中断由上层处理中断。

signal() 方法源码分析

signal() 源码如下:

public final void signal() { //是否当前线程持有锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; //通知"激活"头节点线程 if (first != null) doSignal(first);}

先调用 isHeldExclusively() 判断锁是否被当前线程持有,然后检查等待队列是否为空,不为空就是可以取第一个节点调用 doSignal(first) 去"激活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值