我们一直在说并发编程,我们只知道JDK5有个并发编程包帮我们封装好了多线程下的复杂处理机制,这篇文章的重点不是说它的底层实现原理,只想说下并发包的编程模式思想,我以前一直在想这种思想来源于哪里,面向对象编程的好处应该就是能总结一种模式,只有定义了模式理解了模式,我们平常才能更好的理解一些复杂的编程,这就是抽象到理论的过程吧!我们的前辈把这些编程思想作为一种理论抽象成模式的概念,不就是方便我们更好的理解和接受编程的思想吗?不仅如此,在现在开源如火如荼的进行中,我们怎么通过代码来交流?我们怎么来更好的读懂别人的代码,更好的理解别人的意图,我想这和模式是分不开的,只所以说分不开,是因为我觉得,有了前辈们抽象的总结,我们编写一些复杂代码的时候,会有一些类似的思路,二而这种思路,不就是设计模式吗?我们会想着针对什么样的功能使用什么样的模式,才能让更多的人理解我们的意图,理解我设计这个系统编写这段代码的意图。读懂了设计模式你会发现当你去看别人写的系统的时候,你一看就能看出个大概,马上就能梳理出阅读该系统代码的方向性在哪里?说这么多,好像跟我博文的题目没什么关系似的,其实不是的,我是想说,我们平常都在使用Java封装好的并发编程包,是否会去想为什么要这么封装?这样封装有什么好处?然后,设计模式的概念就出来了,对,我就想要说明Java并发编程使用的设计思想是基于哪种设计模式的。
Active Object模式,不知道大家是否听说过,好像不是我们常见的OO23种设计模式之一,所想说下这个模式的结构:
在 Active Object 模式中,主要有以下几种类型的参与者:
- 代 理 (Proxy) :代理是 Active Object 所定义的对于调用者的公共接口。运行时,代理运行在调用者线程的上下文中,负责把调用者的方法调用转换成相应的方法请求 (Method Request),并将其插入相应的 Activation List,最后返回给调用者 Future 对象。
- 方法请求:方法请求定义了方法执行所需要的上下文信息,诸如调用参数等。
- Activation List:负责存储所有由代理创建的,等待执行的方法请求。从运行时来看,Activation List 会被包括调用者线程及其 Active Object 线程并发存取访问,所以,Activation List 实现应当是线程安全的。
- 调度者 (Scheduler):调度者运行在 Active Object 线程中,调度者来决定下一个执行的方法请求,而调度策略可以基于很多种标准,比如根据方法请求被插入的顺序 FIFO 或者 LIFO,比如根据方法请求的优先级等等。
- Servant: Servant 定义了 Active Object 的行为和状态,它是 Proxy 所定义的接口的事实实现。
- Future: 调用者调用 Proxy 所定义的方法,获得 Future 对象。调用者可以从该 Future 对象获得方法执行的最终结果。在真实的实现里,Future 对象会保留一个私有的空间,用来存放 Servant 方法执行的结果。
我 们可以从 Active Object 设计模式的角度来审视一下 Java Executor 框架。Java Executor 框架以任务 (Task) 为中心,简化了 Active Object 中的角色分工。可以看到,实现 Runnable 或者 Callable 接口的 Java Executor 任务整合了 Method Request 和 Servant 的角色 , 通过实现 run() 或者 call() 方法实现应用逻辑。Java Executor 框架并没有显式地定义 Proxy 接口,而是直接调用 Executor 提交任务,这里的 Executor 相当于 Active Object 中调度者角色。从调用者的角度来看,这看起来并不像是在调用一个普通对象方法,而是向 Executor 提交了一个任务。所以,在这个层面上说,并发的底层细节已经暴露给了调用者。对于 Java 的开发者来说,如果你不担心这样的底层并发细节直接暴露给调用者,或者说你的应用并不需要像对待普通对象一样对待并发对象,Java 的 Executor 框架是一个很好的选择。相反,如果你希望隐藏这样的并发细节,希望像操纵普通对象一样操纵并发对象,那你就需要如本文上节所描述的那样,遵循 Active Object 设计原则 , 清晰地定义各个角色,实现自己的 Active Object 模式。
总而言之,Java Executor 框架简化了 Active Object 所定义的模式,模糊了 Active Object 中角色的分工,其基于生产者 / 消费者模式,生产者和消费者基于任务相互协作。