构建自己的Java并发模型框架
Java的多线程特性为构建高性能的应用提供了极大的方便,可是也带来了不少的麻烦。线程间同步、数据一致性等烦琐的问题须要细心的考虑,一不小心就会出现一些微妙的,难以调试的错误。
另外。应用逻辑和线程逻辑纠缠在一起。会导致程序的逻辑结构混乱,难以复用和维护。
本文试图给出一个解决问题的方案。通过构建一个并发模型框架(framework),使得开发多线程的应用变得easy。
基础知识
普通情况下,对于wait/notify/notifyAll方法的调用都是依据一定的条件来进行的。比方:经典的生产者/消费者问题中对于队列空、满的推断。熟悉POSIX的读者会发现,使用wait/notify/notifyAll能够非常easy的实现POSIX中的一个线程间的高级同步技术:条件变量。
简单样例
类结构图例如以下:
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的两个版本号:多线程版本号和单线程版本号(有些地方,比方其它项目,可能没有并发的问题)。easy带来同步更新和正确选择版本号的问题,给维护带来麻烦。
- 假设多个并发的Client频繁调用该服务。因为是直接同步调用,会造成Client堵塞,减少服务质量。
- 非常难进行一些灵活的控制,比方:依据Client的优先级进行排队等等。
这些问题对于大型的多线程应用server尤为突出,对于一些简单的应用(如本文中的样例)可能根本不用考虑。本文正是要讨论这些问题的解决方式。文中的简单的样例仅仅是提供了一个说明问题。展示思路、方法的平台。
怎样才干较好的解决这些问题,有没有一个能够重用的解决方式呢?让我们先把这些问题放一放,先来谈谈和框架有关的一些问题。
框架概述
它们相当程度的影响了你的程序的形貌。框架本身规划了应用程序的骨干,让程序遵循一定的流程和动线。展现一定的风貌和功能。
这样就使程序猿不必费力于通用性的功能的繁文缛节,集中精力于专业领域。
有一点必需要强调,放之四海而皆准的框架是不存在的。也是最没实用处的。框架往往都是针对某个特定应用领域的。是在对这个应用领域进行深刻理解的基础上,抽象出该应用的概念模型。在这些抽象的概念上搭建的一个模型,是一个有形无体的框架。不同的详细应用依据自身的特点对框架中的抽象概念进行实现,从而赋予框架生命。完毕应用的功能。
基于框架的应用都有两部分构成:框架部分和特定应用部分。要想达到框架复用的目标,必需要做到框架部分和特定应用部分的隔离。
使用面向对象的一个强大功能:多态。能够实现这一点。在框架中完毕抽象概念之间的交互、关联,把详细的实现交给特定的应用来完毕。
当中一般都会大量使用了Template Method设计模式。
构建框架
首先来介绍一个概念,活动对象(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定制排队机制,比方基于优先级等。
基于框架的解决方式
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="">
希望读者可以依据自己的实际情况进行推断。
结论
本文环绕一个简单的样例论述了怎样构架一个Java并发模型框架,当中使用了一些构建框架的经常使用技术。当然所构建的框架和一些成熟的商用框架相比,显得非常稚嫩。比方没有考虑服务调用有返回值的情况,可是其思想方法是一致的,希望读者能够深加领会,这样不管对于构建自己的框架还是理解一些其它的框架都是非常有帮助的。
读者能够对本文中的框架进行扩充,直接应用到自己的工作中。參考文献〔1〕中对于构建并发模型框架中的非常多细节问题进行了深入的论述。有兴趣的读者能够自行研究。以下列出本框架的优缺点:
长处:
增强了应用的并发性,简化了同步控制的复杂性
服务的请求和服务的运行分离,使得能够对服务请求排队,进行灵活的控制
应用逻辑和并发模型分离,使得程序结构清晰。易于维护、重用
能够使开发人员集中精力于应用领域
缺点:
因为框架所需类的存在,在一定程度上添加了程序的复杂性
假设应用须要过多的活动对象,因为线程切换开销会造成性能下降
可能会造成调试困难
构建自己的Java并发模型框架的更多相关文章
- 构建Java并发模型框架
Java的多线程特性为构建高性能的应用提供了极大的方便,但是也带来了不少的麻烦.线程间同步.数据一致性等烦琐的问题需要细心的考虑,一不小心就会出现一些微妙的,难以调试的错误.另外,应用逻辑和线程逻辑纠 ...
- Java并发模型框架
构建Java并发模型框架 Java的多线程特性为构建高性能的应用提供了极大的方便,但是也带来了不少的麻烦.线程间同步.数据一致性等烦琐的问题需要细心的考虑,一不小心就会出现一些微妙的,难以调试的错误. ...
- 一网打尽 Java 并发模型
本篇文章我们来探讨一下并发设计模型. 可以使用不同的并发模型来实现并发系统,并发模型说的是系统中的线程如何协作完成并发任务.不同的并发模型以不同的方式拆分任务,线程可以以不同的方式进行通信和协作. 并 ...
- Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)
AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...
- Java并发模型(一)
学习资料来自http://ifeve.com/java-concurrency-thread-directory/ 一.多线程 进程和线程的区别: 一个程序运行至少一个进程,一个进程至少包含一个线程. ...
- golang常见的几种并发模型框架
原文链接 package main import ( "fmt" "math/rand" "os" "runtime" ...
- Java并发包下锁学习第二篇Java并发基础框架-队列同步器介绍
Java并发包下锁学习第二篇队列同步器 还记得在第一篇文章中,讲到的locks包下的类结果图吗?如下图: 从图中,我们可以看到AbstractQueuedSynchronizer这个类很重要(在本 ...
- java并发编程框架 Executor ExecutorService invokeall
首先介绍两个重要的接口,Executor和ExecutorService,定义如下: public interface Executor { void execute(Runnable command ...
- Java并发编程基础之volatile
首先简单介绍一下volatile的应用,volatile作为Java多线程中轻量级的同步措施,保证了多线程环境中“共享变量”的可见性.这里的可见性简单而言可以理解为当一个线程修改了一个共享变量的时候, ...
随机推荐
- 利用SQL语句实现分页
1.概述 在网页中如果显示的数据太多就会占据过多的页面,而且显示速度也会很慢.为了控制每次在页面上显示数据的数量,就可以利用分页来显示数据. 2.技术要点 在SQL Server中要实现SQL分页,需 ...
- 为什么EXE文件出现了不该出现的“盾牌”
下载了一个小程序,它的功能并不需要管理员权限.但是在Win7下面它的图标上出现了一个“小盾牌”,这意味着运行它需要提升权限……果然,双击时弹出了UAC对话框.用二进制编辑器打开这个EXE,发现它没有内 ...
- asp.net 下载任意格式文件 上传文件后台代码
思路:将文件转化为流,输出到页面上的iframe中去 //下载附件逻辑 object DownLoad(NameValueCollection nv) { int attachId = nv[&quo ...
- 《powershell 的版本号所引起的载入 FSharp 编译器问题》基本解决
<powershell 的版本号所引起的载入 FSharp 编译器问题>基本解决 1.FSharp.Core.dll.不光要 Add-Type,还要在编译中引用.可是,在 VS2012 的 ...
- 每天一个JavaScript实例-动态省份选择城市
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- JMS的样例
1.JMS是一个由AS提供的Message服务.它能接受消息产生者(Message Provider)所发出的消息,并把消息转发给消息消费者(Message Consumer).2.JMS提供2种类 ...
- JAVA - 优雅的记录日志(log4j实战篇) (转)
写在前面 项目开发中,记录错误日志有以下好处: 方便调试 便于发现系统运行过程中的错误 存储业务数据,便于后期分析 在java中,记录日志有很多种方式: 自己实现 自己写类,将日志数据,以io操作方式 ...
- LeetCode My Solution: Minimum Depth of Binary Tree
Minimum Depth of Binary Tree Total Accepted: 24760 Total Submissions: 83665My Submissions Given a bi ...
- JavaScript(15)jQuery 选择器
jQuery 选择器 选择器同意对元素组或单个元素进行操作. jQuery 元素选择器和属性选择器同意通过标签名.属性名或内容对 HTML 元素进行选择. 在 HTML DOM 术语中:选择器同意对 ...
- CodeForces 484B Maximum Value
意甲冠军: a序列n(2*10^5)数字 问道a[i]>=a[j]如果是 a[i]%a[j]最大值是多少 思路: 感觉是一道挺乱来的题-- 我们能够将ans表示为a[i]-k*a[j] 这 ...