快捷搜索:

构建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中的一个线程间的高档同步技巧:前提变量。

有很多的册本、资料对付synchronized、wait/notify/notifyAll进行了具体的先容,参考文献〔3〕中对付synchronized关键字以及和线程有关的Java内存模型有深入具体的叙述,有兴趣的读者可以自行进修,不在此赘述。

简单例子

本文将环抱一个简单的例子展开叙述,这样可以更轻易凸起我们办理问题的思路、措施。本文想向读者展现的恰是这些思路、措施。这些思路、措施加倍适用于办理大年夜规模、繁杂利用中的并发问题。

斟酌一个简单的例子,我们有一个办事供给者,它经由过程一个接口对外供给办事,办事内容异常简单,便是在标准输出上打印Hello World。类布局图如下:

代码如下:

interface Service

{

public void sayHello();

}

class ServiceImp implements Service

{

public void sayHello() {

System.out.println("Hello World!");

}

}

class Client

{

public Client(Service s) {

_service = s;

}

public void requestService() {

_service.sayHello();

}

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(_queue.size() > QUEUE_SIZE) {

try {

wait();

}catch (InterruptedException e) {

e.printStackTrace();

}

}

_queue.push(mr);

notifyAll();

System.out.println("Leave Queue");

}

public synchronized MethodRequest dequeue() {

MethodRequest mr;

while(_queue.empty()) {

try {

wait();

}catch (InterruptedException e) {

e.printStackTrace();

}

}

mr = (MethodRequest)_queue.pop();

notifyAll();

return mr;

}

private Stack _queue;

pri

您可能还会对下面的文章感兴趣: