我看到的核心问题是(a)避免死锁和(b)在线程之间交换数据。出租人的一个担忧(但只是轻微出租人)是避免瓶颈。我已经遇到了几个不同的无序锁定导致死锁的问题——说“总是以相同的顺序获取锁”很好,但在中型到大型系统中,实际上通常不可能确保这一点。
警告:当我想出这个解决方案时,我必须以 Java 1.1 为目标(所以并发包在 Doug Lea 眼中还不是一闪而过)——手头的工具完全同步并等待/通知。我借鉴了使用基于实时消息的系统 QNX 编写复杂的多进程通信系统的经验。
根据我使用 QNX 的经验,它存在死锁问题,但通过将消息从一个进程的内存空间处理到另一个进程来避免数据并发,我提出了一种基于消息的对象方法——我称之为 IOC,用于对象间协调. 在开始时,我设想我可能会像这样创建所有对象,但事后看来,它们仅在大型应用程序的主要控制点是必需的——如果你愿意的话,“州际交换”并不适用于每一个道路系统中的“十字路口”。事实证明这是一个主要的好处,因为它们完全不是 POJO。
我设想了一个系统,其中对象在概念上不会调用同步方法,而是“发送消息”。消息可以是发送/回复,发送方在处理消息时等待并返回回复,也可以是异步的,其中消息被放入队列并在稍后阶段出列并处理。请注意,这是一个概念上的区别——消息传递是使用同步方法调用实现的。
消息系统的核心对象是IsolatedObject、IocBinding 和IocTarget。
隔离对象之所以被称为是因为它没有公共方法;这是为了接收和处理消息而扩展的。使用反射进一步强制子对象没有公共方法,也没有任何包或受保护的方法,除了那些从 IsolatedObject 继承的方法,几乎所有这些方法都是最终的;起初它看起来很奇怪,因为当您继承 IsolatedObject 时,您创建了一个具有 1 个受保护方法的对象:
Object processIocMessage(Object msgsdr, int msgidn, Object msgdta)
其余所有方法都是处理特定消息的私有方法。
IocTarget 是一种抽象IsolatedObject 可见性的方法,对于为另一个对象提供自我引用以将信号发回给您,而不暴露您的实际对象引用非常有用。
IocBinding 只是将发送者对象绑定到消息接收者,这样就不会对发送的每条消息进行验证检查,而是使用 IocTarget 创建的。
与隔离对象的所有交互都是通过“发送”消息来实现的——接收者的 processIocMessage 方法是同步的,这确保了一次只处理一条消息。
Object iocMessage(int mid, Object dta)
void iocSignal (int mid, Object dta)
在创建了一个孤立对象完成的所有工作都通过一个方法汇集的情况之后,我接下来通过它们在构造时声明的“分类”将对象排列在声明的层次结构中 - 只是一个将它们标识为其中之一的字符串任意数量的“消息接收器类型”,将对象置于某个预定的层次结构中。然后我使用消息传递代码来确保如果发送者本身是一个独立对象,对于同步发送/回复消息,它是层次结构中较低的一个。异步消息(信号)使用线程池中的单独线程分派给消息接收者,线程池的整个工作传递信号,因此信号可以从任何对象发送到系统中的任何接收者。信号可以传递任何所需的消息数据,
因为消息只能向上传递(并且信号总是向上传递,因为它们是由专门为此目的运行的单独线程传递的)死锁在设计上被消除了。
因为线程之间的交互是通过使用 Java 同步交换消息来完成的,所以设计上同样消除了竞争条件和陈旧数据的问题。
因为任何给定的接收器一次只处理一条消息,并且因为它没有其他入口点,所以消除了对对象状态的所有考虑——实际上,对象是完全同步的,并且同步不会意外地被任何方法遗漏;没有 getter 返回过时的缓存线程数据,也没有 setter 在另一个方法作用于它时更改对象状态。
因为只有主要组件之间的交互通过这种机制汇集,所以在实践中这已经很好地扩展了——这些交互在实践中几乎没有我理论的那样经常发生。
整个设计成为以严格控制的方式交互的有序子系统集合之一。
请注意,这不适用于更简单的情况,即使用更传统的线程池的工作线程就足够了(尽管我经常通过发送 IOC 消息将工作线程的结果注入主系统)。它也不用于线程关闭并执行完全独立于系统其余部分的事情的情况,例如 HTTP 服务器线程。最后,它不用于资源协调器本身不与其他对象交互以及内部同步将完成工作而没有死锁风险的情况。
编辑:我应该说交换的消息通常应该是不可变的对象;如果使用可变对象,则发送它的行为应被视为移交并导致发送者放弃所有控制权,并且最好不保留对数据的引用。就个人而言,我使用了一个可锁定的数据结构,它被 IOC 代码锁定,因此在发送时变得不可变(锁定标志是易失的)。