当前位置: 首页 > 编程日记 > 正文

构建Java并发模型框架

2002 年 2 月 22 日

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没有需要保护的数据,但是随着需求的变化,以后可能会有的)。但是这样做至少会存在以下几个问题:

  1. 现在要维护ServiceImp的两个版本:多线程版本和单线程版本(有些地方,比如其他项目,可能没有并发的问题),容易带来同步更新和正确选择版本的问题,给维护带来麻烦。
  2. 如果多个并发的Client频繁调用该服务,由于是直接同步调用,会造成Client阻塞,降低服务质量。
  3. 很难进行一些灵活的控制,比如:根据Client的优先级进行排队等等。

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

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





回页首


框架概述

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

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

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

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

基于框架的应用都有两部分构成:框架部分和特定应用部分。要想达到框架复用的目标,必须要做到框架部分和特定应用部分的隔离。使用面向对象的一个强大功能:多态,可以实现这一点。在框架中完成抽象概念之间的交互、关联,把具体的实现交给特定的应用来完成。其中一般都会大量使用了Template Method设计模式。

Java中的Collection Framework以及微软的MFC都是框架方面很好的例子。有兴趣的读者可以自行研究。





回页首


构建框架

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

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

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

本框架有如下几部分构成:

  1. 一个ActiveObject类,从Thread继承,封装了并发逻辑的活动对象
  2. 一个ActiveQueue类,主要用来存放调用者请求
  3. 一个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  ServiceProxy();Client c = new Client(s);c.requestService();

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


图二

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





回页首


结论

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

优点:

  1. 增强了应用的并发性,简化了同步控制的复杂性
  2. 服务的请求和服务的执行分离,使得可以对服务请求排队,进行灵活的控制
  3. 应用逻辑和并发模型分离,使得程序结构清晰,易于维护、重用
  4. 可以使开发者集中精力于应用领域

缺点:

  1. 由于框架所需类的存在,在一定程度上增加了程序的复杂性
  2. 如果应用需要过多的活动对象,由于线程切换开销会造成性能下降
  3. 可能会造成调试困难


参考资料

[1] Active Object An Object Behavioral Pattern for Concurrent, Schmidt, Douglass, Lavender, R. Greg, Programming, http://www.cs.wustl.edu/~schmidt/PDF/Act-Obj.pdf

[2] Effective Java Programming Language Guide, Joshua Bloch

[3] Double-checked locking: Clever, but broken, Brian Goetz, http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.htm

[4] Design Patterns, Gamma, et. al., Addison Wesley

转载于:https://www.cnblogs.com/licheng/archive/2008/09/23/1296829.html

相关文章:

Unity Note 1

1.把开始时间设定到播放完成的时间点,作为倒放的起点 animation["clip"].timeanimation["clip"].clip.length; animation["clip"].speed-1; animation.Play("clip"); 2.寻找场景中物体var door GameObject.Find(…

基于matlab的硅晶体模型,基于Matlab的图像处理技术识别硅太阳电池的缺陷

第 44 卷 第 7 期  2010 年 7 月 上 海 交 通 大 学 学 报 JOURNAL OF SHANGHAI J IAOTON G UNIVERSITY Vol. 44 No. 7   Jul. 2010   收稿日期 :20090908 作者简介 :柳效辉(19852) ,男 ,江西九江人 ,硕士生 ,主要从事光伏检测与光伏系统方面的研究. 徐  林(联系人) ,男 ,副…

spark- PySparkSQL之PySpark解析Json集合数据

PySparkSQL之PySpark解析Json集合数据 数据样本 12341234123412342|asefr-3423|[{"name":"spark","score":"65"},{"name":"airlow","score":"70"},{"name":"flume",&quo…

cmd库的导入Java,在cmd命令窗口导入第三方jar包来运行java文件

在cmd命令窗口导入第三方jar包来运行java文件,以下测试都是基于window环境,Linux环境没有测试。1、编译使用命令javac -cp或者javac -classpath本机测试:如下图所示,java文件路径为D:\workspace\demo,StringUtilsTest.java依赖了第…

JQuery 动态创建表单,并自动提交

前言:写这个是为了实现使用cookie进行自动登录的功能, 下面的代码是一个元素一个元素进行创建和赋值的, (可以尝试下将所有的html代码(form、input)全部拼好以后放到${ } 中,再进行提交。) submit的时候注意下写法&…

(转)利用ArcScene进行三维地形模拟

本文摘自:http://www.sunzx.net/archive/1109.html 在ArcGIS Desktop中,可用于三维场景展示的程序为ArcGlobe和ArcScene,由于两者的差别,在三维场景展示中适用的情况有所不同。ArcScene是一个适合于展示三维透视场景的平台&#x…

Android使用自定义View时:Error inflating class错误的原因。

当在布局文件里使用自定义的View的时候,出现Error inflating class错误的原因: 1、没有定义inflate需要的默认构造函数; eg:自定义View为TestView,需要定义TestView(Context context),TestView(Context context,AttributeSet set); 2、这是个…

oracle的表几种连接比较,几种表连接方式的使用场景

1)nested loopnested loop,指的是两个表连接时, 通过两层嵌套循环来进行依次的匹配, 最后得到返回结果集的表连接方法.select t1.owner,t1.object_name,t2.OBJECT_IDfrom test_tab1 t1,test_tab2 t2where t1.OBJECT_ID t2.OBJECT_IDand ROWNUM select *from test_t…

Ajax 完整教程 (转)

Ajax 完整教程第 1 页 Ajax 简介Ajax 由 HTML、JavaScript™ 技术、DHTML 和 DOM 组成,这一杰出的方法可以将笨拙的 Web 界面转化成交互性的 Ajax 应用程序。本文的作者是一位 Ajax 专家,他演示了这些技术如何协同工作 —— 从总体概述到细节的讨论 ——…

.Net中如何操作IIS(源代码)

http://www.daima.com.cn/Info/3/Info20453/转载于:https://www.cnblogs.com/luoyuan/archive/2005/09/17/238986.html

Enterprise Library Configuration DAAB的使用

1.要试用DAAB,首先要引用两个类库 第一个是Enterprise Library Shared Library 这个类库是所有Enterprist Library都必须引用的类库,它提供所需的结构类型. 第二个是Enterprist Library Data Access Application Block 这个就是daab的核心类库. 2试用DAAB的第一个步骤就是配置a…

安装oracle后在cmd,在WINDOWS上安装ORACLE RAC的注意事项

在WINDOWS上安装ORACLE RAC的注意事项1、检查防火墙和杀毒软件如果不关掉防火墙,在安装CRS时,在"Oracle Clusterware Configuration Assistant"界面会提示(1)OUI-25031错误(2)dddb1 service OracleCSService in improper PENDING state, err(9…

Tessellation (曲面细分) Displacement Mapping (贴图置换)

DirectX 11 Tessellation (曲面细分)—什么是 Tessellation (曲面细分) ?它为什么可以起到如此关键的数据?随着近期人们对 DirectX 11 的议论纷纷,你可能已经听说了有关 DirectX 11 最大新特性 Tessellation (曲面细分) 的大量介绍。作为一个概念。 Tessellation …

java 第12课

/*Java是面向对象的程序设计语言.面向对象的思想是将客观事物都作为实体,而对象通过实体抽象得到.所谓实体抽象,就是对实体的某些特征进行概括,使其数字化、符号化;比如:李四同学,就是一个实体,我们关心他的这些特征:姓名、性别、年龄、身高、体重等特征,就会有李四、男、21、1…

鸽巢原理(The Pigeonhole Principle)(抽屉原理)

简单形式&#xff1a;若n1个物体放进n个盒子&#xff0c;那么至少有一个盒子包含两个或更多的物体。 应用&#xff1a;给定m个整数A1,A2,...,Am,存在整数k和l&#xff0c; 0 < k < l < m,使得Ak1 Ak2 &#xff0b; ... Al能够被m整除。即在A1&#xff0c;A2&…

oracle10g删除asm组,Oracle 10G RAC 删除已有节点

如果现在在RAC集群中有三个节点c1、c2、c3&#xff1a;如果想要卸载c3节点。1、在c1或者c2上删除c3实例运行dbca然后选择Oracle Real Application Clusters database选择Instance Management选择Delete an instance选择实例&#xff0c;填写用户名密码&#xff0c;Next选择c3: …

嵌入式linux学习笔记1—内存管理MMU之虚拟地址到物理地址的转化

一.内存管理基本知识 1.S3C2440最多会用到两级页表&#xff1a;以段的方式进行转换时只用到一级页表&#xff0c;以页的方式进行转换时用到两级页表。页的大小有三种&#xff1a;大页&#xff08;64KB&#xff09;&#xff0c;小页&#xff08;4KB&#xff09;&#xff0c;极小…

C# 最快的逐一打印斐波那契结果数列的算法

用这种方法就无需将数列中的每一个元素都计算一遍了&#xff01; 说多无谓&#xff0c;直接上代码吧&#xff01; private void button5_Click(object sender, EventArgs e) { FiBoNaQi f new FiBoNaQi(); f.numberToCount (Int16)numericUpDown1.Value; f.DoFiB…

WSS 代码执行的权限提升

WSS 代码执行的权限提升 概述: WSS 默认使用身份模拟执行代码&#xff0c;也就是说用当前登录的用户身份执行Web Part或者自定义应用程序的代码访问。在大多数情况下&#xff0c;这种机制能够准确并严格地控制了标准权限的用户他对特定网站资源和敏感数据的访问&#xff0c;这也…

Oracle数据库联邦,使用联邦数据库将oracle表迁移到DB2(9.7)中的脚本说明

由于兄弟项目组要测试&#xff0c;需要将oracle中的表迁移到db2中&#xff0c;操作步骤如下&#xff1a;#1 在windows数据库中建联邦数据库服务器\用户映射connect to sampleCREATE WRAPPER DRDA LIBRARY db2drda.dll;--创建DB2包装器CREATE WRAPPER NET8 LIBRARY db2net8.dll;…

HDU 5047 Sawtooth 高精度

题意&#xff1a; 给出一个\(n(0 \leq n \leq 10^{12})\)&#xff0c;问\(n\)个\(M\)形的折线最多可以把平面分成几部分。 分析&#xff1a; 很容易猜出来这种公式一定的关于\(n\)的一个二次多项式。 不妨设\(f(n)an^2bnc\)。 结合样例我们可以列出\(3\)个方程&#xff1a;\(f(…

poj1129Channel Allocation

http://poj.org/problem?id1129 四色定理 最多有四色 从1到四搜 View Code 1 #include <iostream>2 #include<cstdio>3 #include<cstring>4 #include<stdlib.h>5 using namespace std;6 int n,w[100][100],co[100],mi,flag;7 void dfs(int x,int v)…

WCF 第二章 契约

在原子和金钱世界中&#xff0c;契约是两个或多个组织以一个已知的价格提供商品和服务的合同。在比特和服务的世界中&#xff0c;契约有类似的功能:它是两个或多个组织之间确定消息交换和消息条款及条件的合同。 契约是由服务终结点发送或接收的消息的描述。每一个终结点都由AB…

织梦 新建 php arclist,织梦arclist按照自定义字段来调用相关文章

织梦arclist按照自定义字段来调用相关文章&#xff0c;这对于想要在首页调用某个自定义字段的文章的同学来讲&#xff0c;非常不错&#xff0c;接下来看教程打开 include aglibrclist.lib.php 找到&#xff1a;//时间限制(用于调用最近热门文章、热门评论之类)&#xff0c;这里…

提高php编程效率的小结

1.如果将类的方法定义为&#xff1a;static,它的执行效率将提升为近4倍 2.php中数组的元素调用&#xff0c;使用关联数组优于索引数组 3.使用each快于print. 4.尽量使用foreach()替代for(). 5.销毁那些不用的变量尤其是大数组&#xff0c;如&#xff1a;unset().以便释放内存 6…

摄像机的几个重要的技术指标

(1)清晰度 清晰度是一个摄像机的最重要指标&#xff0c;在监控系统中对图像的清晰度有很高的要求&#xff0c;如在交通监控中,对车辆要能看清车牌号码&#xff0c;对行人要能看清脸部特征&#xff0c;如果这些都看不清楚&#xff0c;那么监控将失去意义。线数的多少决定着清晰度…

Docker容器入门-基本命令的使用

目前容器技术使用相当广泛 不会或者没有使用过容器感觉都不像是个搞技术的 所以&#xff0c;我也就docker相关内容做一个整理 只有不断的学习&#xff0c;才能保持自己的竞争力 什么是容器&#xff1f; 容器是一种轻量级、可移植、自包含的软件打包技术&#xff0c;使应用程序可…

卸载linux系统装win,如何在计算机上删除 Linux 并安装 Windows

多个 IDE 驱动器Device Boot Start End Blocks Id System/dev/hda1 * 1 500 4016218 83 Linux native (IDE hard drive 1, partition 1)/dev/hda2 501 522 176715 82 Linux swap (IDE hard drive 1, partition 2)/dev/hdb1 1 500 4016218 83 Linux native (IDE hard drive 2, p…

卡尔曼滤波— Constant Velocity Model

假设你开车进入隧道&#xff0c;GPS信号丢失&#xff0c;现在我们要确定汽车在隧道内的位置。汽车的绝对速度可以通过车轮转速计算得到&#xff0c;汽车朝向可以通过yaw rate sensor(A yaw-rate sensor is a gyroscopic device that measures a vehicle’s angular velocity ar…

优化实战:不要随便将字段折腾来折腾去的

到新公司先看了看数据库的性能&#xff0c;查看一个存储占用的CPU巨多&#xff0c;而且执行次数也特别多&#xff0c;打开一看&#xff1a;alterPROCEDURE[dbo].[IPLogInsert]IPchar(15) 255.255.255.255ASBEGINSETNOCOUNT ON; declarecurrIdintdeclaretodaydatetime--SET cur…