Java读源码之ReentrantLock(2) (2)

Condition 类似于 Object 中的 wait 和 notify ,主要用于线程间通信,最大的优势是 Object 的 wait 是把线程放到当前对象的等待池中,也就是说一个对象只能有一个等待条件,而 Condition 可以支持多个等待条件,举个例子,商品要等至少三个人预定了才开始发售,第一个预定的减500,第二三两个减100。正式发售之后恢复原价。

public class ReentrantLockConditionDemo { private final ReentrantLock reentrantLock = new ReentrantLock(); private final Condition wait1 = reentrantLock.newCondition(); private final Condition wait2 = reentrantLock.newCondition(); private int wait1Count = 0; private int wait2Count = 0; public void buy() { int price = 999; reentrantLock.lock(); try { while (wait1Count++ < 1) { System.out.println(Thread.currentThread().getName() + "减500"); wait1.await(); price -= 500; } wait1.signal(); while (wait2Count++ < 2) { System.out.println(Thread.currentThread().getName() + "减100"); wait2.await(); price -= 100; } wait2.signal(); System.out.println(Thread.currentThread().getName() + "到手价" + price); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); ReentrantLockConditionDemo reentrantLockConditionDemo = new ReentrantLockConditionDemo(); IntStream.rangeClosed(0, 4) .forEach(num -> executorService .execute(reentrantLockConditionDemo::buy) ); } /** * 输出: * * pool-1-thread-1减500 * pool-1-thread-2减100 * pool-1-thread-3减100 * pool-1-thread-4到手价999 * pool-1-thread-5到手价999 * pool-1-thread-1到手价499 * pool-1-thread-2到手价899 * pool-1-thread-3到手价899 */ }

ReentrantLock#newCondition

先来看条件的创建,需要基于锁对象使用 newCondition 去创建

public Condition newCondition() { return sync.newCondition(); } final ConditionObject newCondition() { // ConditionObject 是 AQS 中对 Condition 的实现 return new ConditionObject(); } ConditionObject结构

上一篇文章中介绍了 Node 结构,这里条件也使用了这个节点定义了一个单链表,统称为条件队列,上一篇介绍统称同步队列。条件队列结构相当简单就不单独画图了。

// 条件队列头 private transient Node firstWaiter; // 条件队列尾 private transient Node lastWaiter; // 因为默认感知中断,需要考虑如何处理 // 退出条件队列时重新设置中断位 private static final int REINTERRUPT = 1; // 退出条件队列时直接抛异常 private static final int THROW_IE = -1; 条件队列入队

AbstractQueuedSynchronizer.ConditionObject#await

public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 到条件队列中排队,下文详解 Node node = addConditionWaiter(); // 此方法比较简单,就是调用前一篇讲过的 release 方法释放锁(调用 await 时必定是锁的持有者) // savedState 是进入条件队列前,持有锁的数量 // 失败会直接抛出异常,并且最终把节点状态设置为 CANCELLED int savedState = fullyRelease(node); int interruptMode = 0; // 判断在不在同步队列(当调用signal之后会从条件队列移到同步队列),此判断很简单:节点状态是 CONDITION 肯定 false,否则就到同步队列中去找 while (!isOnSyncQueue(node)) { // 挂起 LockSupport.park(this); // 检查是不是因为中断被唤醒的,下文详解 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 上一篇介绍过acquireQueued自旋抢锁,如果抢到锁了,并且中断模式不是 -1(默认0),就记录中断模式为1,表示需要重新设置中断 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; // 清除条件队列中取消的节点 if (node.nextWaiter != null) // 下文详解,在addConditionWaiter方法中也有用到 unlinkCancelledWaiters(); // 处理中断 if (interruptMode != 0) // 1:再次中断 -1:抛出异常 reportInterruptAfterWait(interruptMode); }

AbstractQueuedSynchronizer.ConditionObject#addConditionWaiter

加入条件队列

private Node addConditionWaiter() { Node t = lastWaiter; // 如果条件队列最后一个节点取消了,就清理 if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; } // 新建一个 waitStatus = -2 的节点 Node node = new Node(Thread.currentThread(), Node.CONDITION); // 下面是简单的单链表操作,之前同步队列入队用的 CAS 操作,因为会有很多线程去抢锁,而线程进入条件队列一定是拿到锁了,不满足条件了,所以不存在并发问题 if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppzdx.html