构建自己的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多线程中轻量级的同步措施,保证了多线程环境中“共享变量”的可见性.这里的可见性简单而言可以理解为当一个线程修改了一个共享变量的时候, ...
随机推荐
- 第四章 Spring与JDBC的整合
这里选择的是mysql数据库. 4.1引入aop.tx的命名空间 为了事务配置的需要,我们引入aop.tx的命名空间 <?xml version="1.0" encoding ...
- 显示形状回归算法(ESR)代码介绍
源地址:http://www.thinkface.cn/thread-3704-1-6.html 人脸对齐包括两个部分,分别为训练部分和测试部分.所有的代码基于opencv2.0.(一)训练阶段Ste ...
- hdu2647 逆拓扑,链式前向星。
pid=2647">原文地址 题目分析 题意 老板发工资,可是要保证发的工资数满足每一个人的期望,比方A期望工资大于B,仅仅需比B多1元钱就可以.老板发的最低工资为888元.输出老板最 ...
- [置顶] 深圳华为BSS公共部件 (BI 商业智能 Java Javascript)
深圳华为BSS公共部件 部门招聘 招聘面试地点:大连,西安 工作地点:深圳 时间:2013年9月7日 联系方式:dawuliang@gmail.com 18675538182 有兴趣的同学,可以直接电 ...
- 程序猿的量化交易之路(29)--Cointrader之Tick实体(16)
转载需注明出处:http://blog.csdn.net/minimicall,http://cloudtrade.top Tick:什么是Tick,在交易平台中很常见,事实上就 单笔交易时某仅仅证券 ...
- 日交易41.9亿,B2B的魅力为何不输于B2C、C2C?
在最近两年的电子商务版图中,B2C和C2C可谓大放异彩,相比之下,B2B却显得颇为“低调”,当然,低调并不代表没有影响力,只不过,相比B2C和C2C面向数亿网民而言,B2B只针对企业和商家服务 ...
- Android NDK入门实例 计算斐波那契数列一生成jni头文件
最近要用到Android NDK,调用本地代码.就学了下Android NDK,顺便与大家分享.下面以一个具体的实例计算斐波那契数列,说明如何利用Android NDK,调用本地代码.以及比较本地代码 ...
- Nagios监控系统的安装
环境:centOS 6.5 X86 64位 nagios-4.08 步骤: 1. 最小化安装系统 2. 修改安全特性 关闭SELINUX SELINUX=disabled 清除iptabl ...
- ipv6加英文的中括号访问
加英文的中括号就可以,如[2001:4998:c:e33::1004],我发现这是yahoo首页.但并不是所有IPv6网站都可以通过IPv6地址访问,跟IPv4一样,网站服务器端可以只绑定域名,不接受 ...
- R12 付款过程请求-功能和技术信息 (文档 ID 1537521.1)
In this Document Abstract History Details _afrLoop=2234450430619177&id=1537521.1&dis ...