第14章 构建自定义的同步工具
14.1 状态依赖性的管理
依赖状态的操作可以一直阻塞直到可以继续执行,这比使它们先失败再实现起来要更为方便且更不容易出错。内置的条件队列可以使线程一直阻塞,直到对象进入某个进程可以继续执行的状态,并且当被阻塞的线程可以执行时再唤醒它们。
14.1.1 实例:将前提条件的失败传递给调用者
14.1.2 实列:通过轮询与休眠来实现简单的阻塞
14.1.3 条件队列
条件队列使得一组线程(称之为****等待线程集合**)能够通过某种方式来等待特定得条件变成真。
正如每个Java对象对可以作为一个锁,每个对象同样可以作为一个条件队列,并且Object中的wait、notify和notifyAll方法就构成了内部条件队列的API。对象的内置锁与其内部条件队列是互相关联的。
Object.wait会自动释放锁,并请求操作系统挂起当前线程,从而使其他线程能够获得这个锁并修改对象的状态。
14.2 使用条件队列
14.2.1 条件谓词
将与条件队列相关联的条件谓词以及在这些谓词上等待的操作都写入文档。
每一次wait调用都会隐式地与特定的条件谓词关联起来。当调用某个特定条件谓词的wait时,调用者必须已经持有与条件队列相关的锁,并且这个锁必须保护着构成条件谓词的状态变量。
14.2.2 过早唤醒
当使用条件等待时(例如Object.wait或Condition.await)
- 通常都有一个条件谓词--包括一些对象状态的测试,线程在执行前必须首先通过这些测试。
- 在调用wait之前测试条件谓词,并且从wait中返回时再次进行测试。
- 在一个循环中调用wait
- 确保使用与条件队列相关的锁来保护构成条件谓词的各个状态变量。
- 当调用wait,notify或notifyAll等方法时,一定要持有与条件队列相关的锁。
- 在检查条件谓语之后以及开始执行相应的操作之前,不要释放锁。
14.2.4 通知
每当在等待一个条件时,一定要确保在条件谓词变为真时通过某种方式发出通知。
只有同时满足以下两个条件时,才能用单一的notify而不是notifyAll:
- 所有等待线程的类型都相同。 只有一个条件谓语与条件队列相关,并且每个线程在从wait返回后将执行相同的操作。
- 单进单出。 在条件变量上的每次通知,最多只能唤醒一个线程来执行。
14.2.5 示例:阀门类
14.2.6 子类的安全性问题
封装条件队列
14.2.8 入口协议与出口协议
14.3 显式的Condition对象
特别注意:在Condition对象中,与wait、notify和notifyAll方法对应的分别时await、signal和aignalAll。但是,Condition对Object进行了扩展,因而它也包含wait和notify方法,一定要确保使用正确的版本--await和signal。
14.4 Synchronizer剖析
14.5 AbstractQueuedSynchronizer
14.6 java.util.concurrent同步器类中的AQS
14.6.1 ReentrantLock
14.6.2 Semaphore与CountDownLatch
14.6.3 FutureTask
14.6.4 ReentrantReadWriteLock
总结:
要实现一个依赖状态的类 ---- 如果没有满足依赖状态的前提,那么这个类的方法必须阻塞,那么最好的方式是基于现有的库类来构建,例如Semaphore.BlockingQueue或CountDownLatch。然而,有时候现有的库类不能提供足够的功能,在这种情况下,可以使用内置的条件队列、显示的Condition对象或者AbstractQueueSynchronizer来构建自己的同步器。