- wait 时会释放锁
- signal 会唤醒等待同一 cv 的线程
- 被唤醒的线程需要重新获取锁 , 然后才能从 wait 中返回
- Hoare 监控器(1974) , 最早的监控器实现 , 在调用 signal 后 , 会立刻运行等待的线程 , 这时调用 signal 的线程会被堵塞(因为锁被等待线程占有了)
- Mesa 监控器(Xerox PARC, 1980) , signal 会把等待的线程重新放回到监控的 ready 队列中 , 同时调用 signal 的线程继续执行 。这种方式是现如今 pthreads/Java/C# 采用的

文章插图
上图表示蓝色的线程在调用监控器的 get 方式时 , 数据为空 , 因此开始等待 emptyFull 条件;紧接着 , 红色线程调用监控器的 set 方法改变 emptyFull 条件 , 这时
- 按照 Hoare 思路 , 蓝色线程会立刻执行 , 并且红色线程堵塞
- 按照 Mesa 思路 , 红色线程会继续执行 , 蓝色线程会重新与绿色线程竞争与监控器关联的锁
Big Picture

文章插图
Interruptible通过介绍操作系统支持的同步原语 , 我们知道了 park/unpark、wait/notify 其实就是利用信号量( pthread_mutex_t)、条件变量(pthread_cond_t)实现的 , 其实监控器也可以用信号量来实现 。在查看 AQS 中 , 发现有这么一个属性:
/** * The number of nanoseconds for which it is faster to spin * rather than to use timed park. A rough estimate suffices * to improve responsiveness with very short timeouts. */static final long spinForTimeoutThreshold = 1000L;也就是说 , 在小于 1000 纳秒时 , await 条件变量 P 时 , 会使用一个循环来代替条件变量的堵塞与唤醒 , 这是由于堵塞与唤醒本身的操作开销可能就远大于 await 的 timeout 。相关代码:// AQS 的 doAcquireNanos 方法节选for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return true;}nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L)return false;if (shouldParkAfterFailedAcquire(p, node) &&nanosTimeout > spinForTimeoutThreshold)LockSupport.parkNanos(this, nanosTimeout);if (Thread.interrupted())throw new InterruptedException();}在 JUC 提供的高级同步类中 , acquire 对应 park , release 对应 unpark , interrupt 其实就是个布尔的 flag 位 , 在 unpark 被唤醒时 , 检查该 flag , 如果为 true , 则会抛出我们熟悉的 InterruptedException 。Selector.select() 响应中断异常的逻辑有些特别 , 因为对于这类堵塞 IO 操作来说 , 没有条件变量的堵塞唤醒机制 , 我们可以再看下 Thread.interrupt 的实现
public void interrupt() {if (this != Thread.currentThread())checkAccess();synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {interrupt0();// Just to set the interrupt flagb.interrupt(this);return;}}interrupt0();}OpenJDK 使用了这么一个技巧来实现堵塞 IO 的中断唤醒:在一个线程被堵塞时 , 会关联一个 Interruptible 对象 。对于 Selector 来说 , 在开始时 , 会关联这么一个Interruptible 对象:protected final void begin() {if (interruptor == null) {interruptor = new Interruptible() {public void interrupt(Thread target) {synchronized (closeLock) {if (closed)return;closed = true;interrupted = target;try {AbstractInterruptibleChannel.this.implCloseChannel();} catch (IOException x) { }}}};}blockedOn(interruptor);Thread me = Thread.currentThread();if (me.isInterrupted())interruptor.interrupt(me);}当调用 interrupt 方式时 , 会关闭该 channel , 这样就会关闭掉这个堵塞线程 , 可见为了实现这个功能 , 代价也是比较大的 。LockSupport.park 中采用了类似技巧 。
推荐阅读
- Java5,6,7,8的主要新特性归纳
- Java环境搭建,环境变量配置
- IPv6基础了解和配置
- JavaScript 字符串中的 pad 方法
- Java中的21种锁,图文并茂的详细解释
- Javascript中类型知识和valueOf和toString()方法
- javascript事件流
- 盘点2020JavaScript游戏框架
- Java分布式锁看这篇就够了
- 网络高并发
