本文共 2514 字,大约阅读时间需要 8 分钟。
程序在做某一个操作之前,需要依赖另一个操作的完成或者状态的就绪,这样的一种关系就叫做“状态依赖”。
状态依赖的实现类,就是并发工具的原语。
例如FutureTask、Semaphore和BlockingQueue等。在这些类的一些操作中有着基于状态的前提条件,例如,不能从一个空队列删除元素,或者获取一个尚未结束的任务的计算结果,在这些操作可以执行之前,必须等待队列进入“非空”状态,或者任务进入“已完成”状态。
实现了状态依赖的底层类有:内置的条件队列、显示的Condition对象以及AbstractQueuedSynchronizer框架。
锁机制或者直接使用原子类都能实现应用程序中的原子操作,但原子操作的原语则不能依赖锁机制,必须依赖于操作系统本身才行,否则会陷入先有鸡还是先有蛋的悖论,因为锁机制原语本身是有依赖原子操作的。
阻塞与唤醒、自旋都是实现状态依赖的思路。
它合得一组线程(称之为等待线程集合)能够通过某种方式来等待特定的条件变为真。传统的队列是一个个数据,而与之不同的是,条件队列中的元素是一个个正在等待相关条件的线程。
Object中的wait、notify和notifyAll方法构成了内部条件队列的API。
对象的内置锁与内部条件是相互关联的,要调用对象X中的条件队列的任何一个方法,必须持有对象X上的锁。
这是因为“等待由状态构成的条件”与“维护状态一致性”这两种机制必须被紧密地绑定在一起。只有能对状态进行检查时,才能在某个条件上等待,并能只有能修改状态时,才能从条件等待中释放另一个线程。
条件谓词是使某个操作成为状态依赖操作的前提条件。在有界缓存中,只有当缓存不为空时,take方法才能执行,否则必须等待。对take方法来说,它的条件谓词就是“缓存不为空”,take方法在执行之前必须首先测试该条件谓词。
每一次wait调用都会隐式地与特定的条件谓词关联起来。当调用某个特定条件谓词的wait时,调用者必须已经持有与条件队列相关的锁,并且这个锁必须保护着构成条件谓词的状态变量。
notify或者notifyAll操作发生在wait之前,就会造成通知信号的丢失,最终wait永远都得不到恢复或者不得不等待下一次重新通知而延迟了恢复时间。
注意事项:发出通知的线程应该尽快地释放锁,从而确保正在等待的线程尽可能快地解除阻塞。如果这些等待中线程此时不能重新获得锁,那么无法从wait返回。
由于多个线程可以基于不同的条件谓词在同一个条件队列上等待,因此如果使用notify而不是notifyAll,那么将是一个危险的操作,因为单一的通知很容易导致类似信号丢失的问题。为什么说是类似,因为导致的问题是相同的:线程正在等待一个已经(或者本应该)发生过的信号。
对于状态依赖的类,要么将其等待和通知等协议完全向子类公开(并且写入正式文档),要么完全阻止子类参与到等待与通知等过程中。
这是对“要么围绕着继承来设计和文档化,要么禁止使用继承”这条规则的一种扩展【EJ ITEM 15】
每个内置锁都只能有一个相关联的条件队列,因而在像BoundBuffer这种类中,多个线程可能在同一个条件队列上等待不同的条件谓词,并且在最常见的加锁模式下公开条件队列对象。
特别注意:Condition对象中,三个与条件队列相关的API是:await,signal,signalAll。不要使用错了。
AQS中对于并发状态操作最核心的原语,如线程挂起唤醒、原子操作等,都是通过LockSupport(最终由sun.misc.Unsafe中的native方法)提供。
Unsafe中提供对内存直接操作、序列化、并发原语、CAS操作、线程挂起与唤醒等,不建议在我们自己的代码中使用。
转载地址:http://etlxb.baihongyu.com/