java

位置:IT落伍者 >> java >> 浏览文章

多线程开发的捷径:构建Java并发模型框架


发布日期:2020年08月17日
 
多线程开发的捷径:构建Java并发模型框架

Java多线程特性为构建高性能的应用提供了极大的方便但是也带来了不少的麻烦线程间同步数据一致性等烦琐的问题需要细心的考虑一不小心就会出现一些微妙的难以调试的错误

另外应用逻辑和线程逻辑纠缠在一起会导致程序的逻辑结构混乱难以复用和维护本文试图给出一个解决这个问题的方案通过构建一个并发模型框架(framework)使得开发多线程的应用变得容易

基础知识

Java语言提供了对于线程很好的支持实现方法小巧优雅对于方法重入的保护信号量(semaphore)和临界区(critical section)机制的实现都非常简洁可以很容易的实现多线程间的同步操作从而保护关键数据的一致性这些特点使得Java成为面向对象语言中对于多线程特性支持方面的佼佼者(C++正在试图把boost库中的对于线程的支持部分纳入语言标准)

Java中内置了对于对象并发访问的支持每一个对象都有一个监视器(monitor)同时只允许一个线程持有监视器从而进行对对象的访问那些没有获得监视器的线程必须等待直到持有监视器的线程释放监视器对象通过synchronized关键字来声明线程必须获得监视器才能进行对自己的访问

synchronized声明仅仅对于一些较为简单的线程间同步问题比较有效对于哪些复杂的同步问题比如带有条件的同步问题Java提供了另外的解决方法wait/notify/notifyAll

获得对象监视器的线程可以通过调用该对象的wait方法主动释放监视器等待在该对象的线程等待队列上此时其他线程可以得到监视器从而访问该对象之后可以通过调用notify/notifyAll方法来唤醒先前因调用wait方法而等待的线程

一般情况下对于wait/notify/notifyAll方法的调用都是根据一定的条件来进行的比如经典的生产者/消费者问题中对于队列空满的判断熟悉POSIX的读者会发现使用wait/notify/notifyAll可以很容易的实现POSIX中的一个线程间的高级同步技术条件变量

简单例子

本文将围绕一个简单的例子展开论述这样可以更容易突出我们解决问题的思路方法本文想向读者展现的正是这些思路方法这些思路方法更加适用于解决大规模复杂应用中的并发问题考虑一个简单的例子我们有一个服务提供者它通过一个接口对外提供服务服务内容非常简单就是在标准输出上打印Hello World类结构图如下

)thisstylewidth=; border=>

代码如下

interface Service

{

public void sayHello();

}

class ServiceImp implements Service

{

public void sayHello() {

Systemoutprintln(Hello World!);

}

}

class Client

{

public Client(Service s) {

_service = s;

}

public void requestService() {

_servicesayHello();

}

private Service _service;

}

如果现在有新的需求要求该服务必须支持Client的并发访问一种简单的方法就是在ServicImp类中的每个方法前面加上synchronized声明来保证自己内部数据的一致性(当然对于本例来说目前是没有必要的因为ServiceImp没有需要保护的数据但是随着需求的变化以后可能会有的)但是这样做至少会存在以下几个问题

现在要维护ServiceImp的两个版本多线程版本和单线程版本(有些地方比如其他项目可能没有并发的问题)容易带来同步更新和正确选择版本的问题给维护带来麻烦

如果多个并发的Client频繁调用该服务由于是直接同步调用会造成Client阻塞降低服务质量

很难进行一些灵活的控制比如根据Client的优先级进行排队等等

这些问题对于大型的多线程应用服务器尤为突出对于一些简单的应用(如本文中的例子)可能根本不用考虑本文正是要讨论这些问题的解决方案文中的简单的例子只是提供了一个说明问题展示思路方法的平台

如何才能较好的解决这些问题有没有一个可以重用的解决方案呢?让我们先把这些问题放一放先来谈谈和框架有关的一些问题

框架概述

熟悉面向对象的读者一定知道面向对象的最大的优势之一就是软件复用通过复用可以减少很多的工作量提高软件开发生产率复用本身也是分层次的代码级的复用和设计架构的复用

大家可能非常熟悉C语言中的一些标准库它们提供了一些通用的功能让你的程序使用但是这些标准库并不能影响你的程序结构和设计思路仅仅是提供一些机能帮助你的程序完成工作它们使你不必重头编写一般性的通用功能(比如printf)它们强调的是程序代码本身的复用性而不是设计架构的复用性

那么什么是框架呢?所谓框架它不同于一般的标准库是指一组紧密关联的(类)classes强调彼此的配合以完成某种可以重复运用的设计概念这些类之间以特定的方式合作彼此不可或缺它们相当程度的影响了你的程序的形貌框架本身规划了应用程序的骨干让程序遵循一定的流程和动线展现一定的风貌和功能这样就使程序员不必费力于通用性的功能的繁文缛节集中精力于专业领域

有一点必须要强调放之四海而皆准的框架是不存在的也是最没有用处的框架往往都是针对某个特定应用领域的是在对这个应用领域进行深刻理解的基础上抽象出该应用的概念模型在这些抽象的概念上搭建的一个模型是一个有形无体的框架不同的具体应用根据自身的特点对框架中的抽象概念进行实现从而赋予框架生命完成应用的功能

基于框架的应用都有两部分构成框架部分和特定应用部分要想达到框架复用的目标必须要做到框架部分和特定应用部分的隔离使用面向对象的一个强大功能多态可以实现这一点在框架中完成抽象概念之间的交互关联把具体的实现交给特定的应用来完成其中一般都会大量使用了Template Method设计模式Java中的Collection Framework以及微软的MFC都是框架方面很好的例子有兴趣的读者可以自行研究

构建框架

如何构建一个Java并发模型框架呢?让我们先回到原来的问题先来分析一下原因造成要维护多线程和单线程两个版本的原因是由于把应用逻辑和并发逻辑混在一起如果能够做到把应用逻辑和并发模型进行很好的隔离那么应用逻辑本身就可以很好的被复用而且也很容易把并发逻辑添加进来而不会对应用逻辑造成任何影响造成Client阻塞性能降低以及无法进行额外的控制的原因是由于所有的服务调用都是同步的解决方案很简单改为异步调用方式把服务的调用和服务的执行分离

首先来介绍一个概念活动对象(Active Object)所谓活动对象是相对于被动对象(passive object)而言的被动对象的方法的调用和执行都是在同一个线程中的被动对象方法的调用是同步的阻塞的一般的对象都属于被动对象主动对象的方法的调用和执行是分离的主动对象有自己独立的执行线程主动对象的方法的调用是由其他线程发起的但是方法是在自己的线程中执行的主动对象方法的调用是异步的非阻塞的

本框架的核心就是使用主动对象来封装并发逻辑然后把Client的请求转发给实际的服务提供者(应用逻辑)这样无论是Client还是实际的服务提供者都不用关心并发的存在不用考虑并发所带来的数据一致性问题从而实现应用逻辑和并发逻辑的隔离服务调用和服务执行的隔离下面给出关键的实现细节

本框架有如下几部分构成

一个ActiveObject类从Thread继承封装了并发逻辑的活动对象

一个ActiveQueue类主要用来存放调用者请求

一个MethodRequest接口主要用来封装调用者的请求Command设计模式的一种实现方式它们的一个简单的实现如下

//MethodRequest接口定义

interface MethodRequest

{

public void call();

}

//ActiveQueue定义其实就是一个producer/consumer队列

class ActiveQueue

{

public ActiveQueue() {

_queue = new Stack();

}

public synchronized void enqueue(MethodRequest mr) {

while(_queuesize() > QUEUE_SIZE) {

try {

wait();

}catch (InterruptedException e) {

eprintStackTrace();

}

}

_queuepush(mr);

notifyAll();

Systemoutprintln(Leave Queue);

}

public synchronized MethodRequest dequeue() {

MethodRequest mr;

while(_queueempty()) {

try {

wait();

}catch (InterruptedException e) {

eprintStackTrace();

}

}

mr = (MethodRequest)_queuepop();

notifyAll();

return mr;

}

private Stack _queue;

private final static int QUEUE_SIZE = ;

}

//ActiveObject的定义

class ActiveObject extends Thread

{

public ActiveObject() {

_queue = new ActiveQueue();

start();

}

public void enqueue(MethodRequest mr) {

_queueenqueue(mr);

}

public void run() {

while(true) {

MethodRequest mr = _queuedequeue();

mrcall();

}

}

private ActiveQueue _queue;

}

通过上面的代码可以看出正是这些类相互合作完成了对并发逻辑的封装开发者只需要根据需要实现MethodRequest接口另外再定义一个服务代理类提供给使用者在服务代理者类中把服务调用者的请求转化为MethodRequest实现交给活动对象即可

使用该框架可以较好的做到应用逻辑和并发模型的分离从而使开发者集中精力于应用领域然后平滑的和并发模型结合起来并且可以针对ActiveQueue定制排队机制比如基于优先级等

基于框架的解决方案

本小节将使用上述的框架重新实现前面的例子提供对于并发的支持第一步先完成对于MethodRequest的实现对于我们的例子来说实现如下

class SayHello implements MethodRequest

{

public SayHello(Service s) {

_service = s;

}

public void call() {

_servicesayHello();

}

private Service _service;

}

该类完成了对于服务提供接口sayHello方法的封装接下来定义一个服务代理类来完成请求的封装排队功能当然为了做到对Client透明该类必须实现Service接口定义如下

class ServiceProxy implements Service

{

public ServiceProxy() {

_service = new ServiceImp();

_active_object = new ActiveObject();

}

public void sayHello() {

MethodRequest mr = new SayHello(_service);

_active_objectenqueue(mr);

}

private Service _service;

private ActiveObject _active_object;

}

其他的类和接口定义不变下面对比一下并发逻辑增加前后的服务调用的变化并发逻辑增加前对于sayHello服务的调用方法

Service s = new ServiceImp();

Client c = new Client(s);

crequestService();

并发逻辑增加后对于sayHello服务的调用方法

Service s = new ServiceProxy();

Client c = new Client(s);

crequestService();

可以看出并发逻辑增加前后对于Client的ServiceImp都无需作任何改变使用方式也非常一致ServiceImp也能够独立的进行重用类结构图如下

image onmousewheel=javascript:return big(this) height= alt=类结构图 src=http://imgeducitycn/img_///png width= onload=javascript:if(thiswidth>)thisstylewidth=; border=>

读者容易看出使用框架也增加了一些复杂性对于一些简单的应用来说可能根本就没有必要使用本框架希望读者能够根据自己的实际情况进行判断

结论

本文围绕一个简单的例子论述了如何构架一个Java并发模型框架其中使用了一些构建框架的常用技术当然所构建的框架和一些成熟的商用框架相比显得非常稚嫩比如没有考虑服务调用有返回值的情况但是其思想方法是一致的希望读者能够深加领会这样无论对于构建自己的框架还是理解一些其他的框架都是很有帮助的读者可以对本文中的框架进行扩充直接应用到自己的工作中

优点

增强了应用的并发性简化了同步控制的复杂性

服务的请求和服务的执行分离使得可以对服务请求排队进行灵活的控制

应用逻辑和并发模型分离使得程序结构清晰易于维护重用

可以使开发者集中精力于应用领域

缺点

由于框架所需类的存在在一定程度上增加了程序的复杂性

如果应用需要过多的活动对象由于线程切换开销会造成性能下降

可能会造成调试困难

上一篇:中断JAVA线程

下一篇:java与模式笔记 - 适配器模式