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

另外。应用逻辑和线程逻辑纠缠在一起。会导致程序的逻辑结构混乱,难以复用和维护。

本文试图给出一个解决问题的方案。通过构建一个并发模型框架(framework),使得开发多线程的应用变得easy。

基础知识

Java中内置了对于对象并发訪问的支持,每个对象都有一个监视器(monitor)。同一时候仅仅同意一个线程持有监视器从而进行对对象的訪问,那些没有获得监视器的线程必须等待直到持有监视器的线程释放监视器。对象通过synchronizedkeyword来声明线程必须获得监视器才干进行对自己的訪问。

synchronized声明只对于一些较为简单的线程间同步问题比較有效,对于哪些复杂的同步问题。比方带有条件的同步问题,Java提供了另外的解决方法,wait/notify/notifyAll。获得对象监视器的线程能够通过调用该对象的wait方法主动释放监视器,等待在该对象的线程等待队列上,此时其它线程能够得到监视器从而訪问该对象,之后能够通过调用notify/notifyAll方法来唤醒先前因调用wait方法而等待的线程。

普通情况下,对于wait/notify/notifyAll方法的调用都是依据一定的条件来进行的。比方:经典的生产者/消费者问题中对于队列空、满的推断。熟悉POSIX的读者会发现,使用wait/notify/notifyAll能够非常easy的实现POSIX中的一个线程间的高级同步技术:条件变量。

简单样例

考虑一个简单的样例,我们有一个服务提供者。它通过一个接口对外提供服务,服务内容很easy,就是在标准输出上打印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没有须要保护的数据,可是随着需求的变化。以后可能会有的)。可是这样做至少会存在下面几个问题:

  1. 如今要维护ServiceImp的两个版本号:多线程版本号和单线程版本号(有些地方,比方其它项目,可能没有并发的问题)。easy带来同步更新和正确选择版本号的问题,给维护带来麻烦。
  2. 假设多个并发的Client频繁调用该服务。因为是直接同步调用,会造成Client堵塞,减少服务质量。
  3. 非常难进行一些灵活的控制,比方:依据Client的优先级进行排队等等。

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

怎样才干较好的解决这些问题,有没有一个能够重用的解决方式呢?让我们先把这些问题放一放,先来谈谈和框架有关的一些问题。




框架概述

熟悉面向对象的读者一定知道面向对象的最大的优势之中的一个就是:软件复用。通过复用,能够降低非常多的工作量。提高软件开发生产率。复用本身也是分层次的。代码级的复用和设计架构的复用。

那么什么是框架呢?所谓框架,它不同于一般的标准库,是指一组紧密关联的(类)classes,强调彼此的配合以完毕某种能够反复运用的设计概念。这些类之间以特定的方式合作。彼此不可或缺。

它们相当程度的影响了你的程序的形貌。框架本身规划了应用程序的骨干,让程序遵循一定的流程和动线。展现一定的风貌和功能。

这样就使程序猿不必费力于通用性的功能的繁文缛节,集中精力于专业领域。

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

基于框架的应用都有两部分构成:框架部分和特定应用部分。要想达到框架复用的目标,必需要做到框架部分和特定应用部分的隔离。

使用面向对象的一个强大功能:多态。能够实现这一点。在框架中完毕抽象概念之间的交互、关联,把详细的实现交给特定的应用来完毕。

当中一般都会大量使用了Template Method设计模式。

构建框架

怎样构建一个Java并发模型框架呢?让我们先回到原来的问题,先来分析一下原因。造成要维护多线程和单线程两个版本号的原因是因为把应用逻辑和并发逻辑混在一起,假设可以做到把应用逻辑和并发模型进行非常好的隔离。那么应用逻辑本身就行非常好的被复用,并且也非常容易把并发逻辑加入进来而不会相应用逻辑造成不论什么影响。造成Client堵塞。性能减少以及无法进行额外的控制的原因是因为全部的服务调用都是同步的,解决方式非常easy。改为异步调用方式,把服务的调用和服务的运行分离。

首先来介绍一个概念,活动对象(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;
private final static int QUEUE_SIZE = 20;
}
//ActiveObject的定义
class ActiveObject extends Thread
{
public ActiveObject() {
_queue = new ActiveQueue();
start();
}
public void enqueue(MethodRequest mr) {
_queue.enqueue(mr);
}
public void run() {
while(true) {
MethodRequest mr = _queue.dequeue();
mr.call();
}
}
private ActiveQueue _queue;
}

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

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

基于框架的解决方式

使用上述的框架又一次实现前面的样例。提供对于并发的支持。第一步先完毕对于MethodRequest的实现,对于我们的样例来说实现例如以下:
class SayHello implements MethodRequest
{
public SayHello(Service s) {
_service = s;
}
public void call() {
_service.sayHello();
}
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_object.enqueue(mr);
}
private Service _service;
private ActiveObject _active_object;
}

其它的类和接口定义不变。以下对照一下并发逻辑添加前后的服务调用的变化,并发逻辑添加前。对于sayHello服务的调用方法:

Service s = new ServiceImp();
Client c = new Client(s);
c.requestService();

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

Service s = new ServiceImp();
Client c = new Client(s);
c.requestService();

可以看出并发逻辑添加前后对于Client的ServiceImp都无需作不论什么改变,使用方式也很一致,ServiceImp也可以独立的进行重用。类结构图例如以下:


watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamVzc29ubHY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

读者easy看出,使用框架也添加了一些复杂性。对于一些简单的应用来说可能根本就没有必要使用本框架。

希望读者可以依据自己的实际情况进行推断。


结论

本文环绕一个简单的样例论述了怎样构架一个Java并发模型框架,当中使用了一些构建框架的经常使用技术。当然所构建的框架和一些成熟的商用框架相比,显得非常稚嫩。比方没有考虑服务调用有返回值的情况,可是其思想方法是一致的,希望读者能够深加领会,这样不管对于构建自己的框架还是理解一些其它的框架都是非常有帮助的。

读者能够对本文中的框架进行扩充,直接应用到自己的工作中。參考文献〔1〕中对于构建并发模型框架中的非常多细节问题进行了深入的论述。有兴趣的读者能够自行研究。以下列出本框架的优缺点:

长处:

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

服务的请求和服务的运行分离,使得能够对服务请求排队,进行灵活的控制

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

能够使开发人员集中精力于应用领域

缺点:

因为框架所需类的存在,在一定程度上添加了程序的复杂性

假设应用须要过多的活动对象,因为线程切换开销会造成性能下降

可能会造成调试困难

构建自己的Java并发模型框架的更多相关文章

  1. 构建Java并发模型框架

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

  2. Java并发模型框架

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

  3. 一网打尽 Java 并发模型

    本篇文章我们来探讨一下并发设计模型. 可以使用不同的并发模型来实现并发系统,并发模型说的是系统中的线程如何协作完成并发任务.不同的并发模型以不同的方式拆分任务,线程可以以不同的方式进行通信和协作. 并 ...

  4. Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)

    AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...

  5. Java并发模型(一)

    学习资料来自http://ifeve.com/java-concurrency-thread-directory/ 一.多线程 进程和线程的区别: 一个程序运行至少一个进程,一个进程至少包含一个线程. ...

  6. golang常见的几种并发模型框架

    原文链接 package main import ( "fmt" "math/rand" "os" "runtime" ...

  7. Java并发包下锁学习第二篇Java并发基础框架-队列同步器介绍

    Java并发包下锁学习第二篇队列同步器 还记得在第一篇文章中,讲到的locks包下的类结果图吗?如下图: ​ 从图中,我们可以看到AbstractQueuedSynchronizer这个类很重要(在本 ...

  8. java并发编程框架 Executor ExecutorService invokeall

    首先介绍两个重要的接口,Executor和ExecutorService,定义如下: public interface Executor { void execute(Runnable command ...

  9. Java并发编程基础之volatile

    首先简单介绍一下volatile的应用,volatile作为Java多线程中轻量级的同步措施,保证了多线程环境中“共享变量”的可见性.这里的可见性简单而言可以理解为当一个线程修改了一个共享变量的时候, ...

随机推荐

  1. Indy的TCPServer到底能支持多少个连接

    最近一个项目,最开始使用IdTcpServer,在大压力测试的时候,只连接了800个多一点的客户端(每个客户端连接上之后每秒钟发送一个几十字节的报文,服务器应答).但是持续的时间不会超过10分钟,服务 ...

  2. cannot run program "git.exe":CreateProcess error=2

    在使用android studio从git上check项目的时候报错cannot run program "git.exe":CreateProcess error=2 请检查下面 ...

  3. 开发自己PHP MVC框架(一)

    本教程翻译自John Squibb 的Build a PHP MVC Framework in an Hour,但有所改动,原文地址:http://johnsquibb.com/tutorials 这 ...

  4. [项目整理]Win32,MFC的可执行文件只能运行一次

    //第一种方法:控制release版本的exe文件只能运行一次 #ifndef _DEBUG //debug 版本中,项目属性-->预处理器 -->预处理定义: 有_DEBUG if (F ...

  5. 以JTextPanel为例Swing的鼠标事件详解

    如下界面可以通过该界面研究一下Swing的鼠标事件: 图中用红粗线圈起来的为JtextPanel,该Panel添加了鼠标事件监听器,鼠标事件监听器有三种,分别为MouseWheelListener,M ...

  6. Hibernate学习之createSQLQuery与createQuery的区别及使用

    hibernate中createQuery与createSQLQuery:前者用的hql语句进行查询,后者可以用sql语句查询,前者以hibernate生成的Bean为对象装入list返回,后者则是以 ...

  7. BAT笔试试题常见试题总结含答案(持续更新。。。)

    (1)试题例如以下: class A { int a; short b; int c; char d; }; class B { double a; short b; int c; char d; } ...

  8. hibernate 在tomcat7.X 下配置mysql数据源

    先说一点题外话,LZ近期学习java web. 今天刚看到hibernate,发如今hibernate配置数据源时网上的资料都太久远了,一般以tomcat 5 版本号下的配置居多.而tomcat 7下 ...

  9. 给公司部门设计的SOA架构(转)

    新来老大年前开会说:各位同学,公司业务越来越重,未来几年要成倍增长......,我们要梳理出一套新架构,才能更好的支持N万用户.....,以后升职加薪当上....打败..... 想想还有点小激动呢,于 ...

  10. hdu5172(线段树)

    传送门:GTY's gay friends 题意:判断区间[l,r]内的数字是否符合1~len(r-l+1)的一个全排列. 分析:pos[i]记录数字i出现的最大位置,pre[i]记录在位置i的数字a ...