jsp 多线程开发与Java并发模型框架

基础知识

  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中的一个线程间的高级同步技术:条件变量。

  简单例子

  本文将围绕一个简单的例子展开论述,这样可以更容易突出我们解决问题的思路、方法。本文想向读者展现的正是这些思路、方法。这些思路、方法更加适用于解决大规模、复杂应用中的并发问题。考虑一个简单的例子,我们有一个服务提供者,它通过一个接口对外提供服务,服务内容非常简单,就是在标准输出上打印Hello World。类结构图如下:

开发与Java并发模型框架" class="fit-image" height="170" src="http://filesimg.111cn.net/2010/03/20100506114940984.png" width="351">

  代码如下:

  1.interface Service

  2.{

  3.    public void sayHello();

  4.}

  5.class ServiceImp implements Service

  6.{

  7.    public void sayHello() {

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

  9.    }

  10.}

  11.class Client

  12.{

  13.    public Client(Service s) {

  14.        _service = s;

  15.}

  16.    public void requestService() {

  17.        _service.sayHello();

  18.    }

  19.    private Service _service;

  20.}

  如果现在有新的需求,要求该服务必须支持Client的并发访问。一种简单的方法就是在ServicImp类中的每个方法前面加上synchronized声明,来保证自己内部数据的一致性(当然对于本例来说,目前是没有必要的,因为ServiceImp没有需要保护的数据,但是随着需求的变化,以后可能会有的)。但是这样做至少会存在以下几个问题:

 

  1.现在要维护ServiceImp的两个版本:多线程版本和单线程版本(有些地方,比如其他项目,可能没有并发的问题),容易带来同步更新和正确选择版本的问题,给维护带来麻烦。

  2.如果多个并发的Client频繁调用该服务,由于是直接同步调用,会造成Client阻塞,降低服务质量。

  3.很难进行一些灵活的控制,比如:根据Client的优先级进行排队等等。

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

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

  框架概述

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

  大家可能非常熟悉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设计模式的一种实现方式。它们的一个简单的实现如下:

  1. //MethodRequest接口定义

  2.  interface MethodRequest

  3.{

  4.    public void call();

  5.}

  6.//ActiveQueue定义,其实就是一个producer/consumer队列

  7.    class ActiveQueue

  8.{

  9.      public ActiveQueue() {

  10.        _queue = new Stack();

  11.      }

  12.    public synchronized void enqueue(MethodRequest mr) {

  13.        while(_queue.size() > QUEUE_SIZE) {

  14.            try {

  15.                   wait();

  16.            }catch (InterruptedException e) {

  17.                   e.printStackTrace();

  18.            }

  19.        }

  20.

  21.        _queue.push(mr);

  22.        notifyAll();

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

  24.    }

  25.    public synchronized MethodRequest dequeue() {

  26.        MethodRequest mr;

  27.

  28.        while(_queue.empty()) {

  29.            try {

  30.                wait();

  31.            }catch (InterruptedException e) {

  32.                e.printStackTrace();

  33.            }

  34.        }

  35.        mr = (MethodRequest)_queu.pop();

 

  36.        notifyAll();

  37.

  38. return mr;

  39.    }

  40.    private Stack _queue;

  41.    private final static int QUEUE_SIZE = 20;

  42.}

  43.//ActiveObject的定义

  44.class ActiveObject extends Thread

  45.{

  46.    public ActiveObject() {

  47.        _queue = new ActiveQueue();

  48.        start();

  49.    }

  50.    public void enqueue(MethodRequest mr) {

  51.        _queue.enqueue(mr);

  52.    }

  53.    public void run() {

  54.        while(true) {

  55.            MethodRequest mr = _queue.dequeue();

  56.            mr.call();

  57.        }

  58.    }

  59.    private ActiveQueue _queue;

  60.}

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

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

  

  

  基于框架的解决方案

  本小节将使用上述的框架重新实现前面的例子,提供对于并发的支持。第一步先完成对于MethodRequest的实现,对于我们的例子来说实现如下:

  1.class SayHello implements MethodRequest

  2.{

  3.    public SayHello(Service s) {

  4.        _service = s;

  5.    }

  6.    public void call() {

  7.        _service.sayHello();

  8.    }

  9.    private Service _service;

  10.}

  该类完成了对于服务提供接口sayHello方法的封装。接下来定义一个服务代理类,来完成请求的封装、排队功能,当然为了做到对Client透明,该类必须实现Service接口。定义如下:

  11.class ServiceProxy implements Service

  12.{

  13.    public ServiceProxy() {

  14.        _service = new ServiceImp();

  15.        _active_object = new ActiveObject();

  16.    }

  17.

  18.    public void sayHello() {

  19.        MethodRequest mr = new SayHello(_service);

  20.        _active_object.enqueue(mr);

  21.    }

  22.    private Service _service;

  23.    private ActiveObject _active_object;

  24.}

  其他的类和接口定义不变,下面对比一下并发逻辑增加前后的服务调用的变化,并发逻辑增加前,对于sayHello服务的调用方法:

  25.Service s = new ServiceImp();

  26.Client c = new Client(s);

  27.c.requestService();

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

  28.Service s = new  ServiceProxy();

  29.Client c = new Client(s);

  30.c.requestService();

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

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

  结论

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

  

时间: 2024-09-20 17:32:35

jsp 多线程开发与Java并发模型框架的相关文章

Java 并发/多线程教程(十一)-JAVA内存模型

本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获.由于个人水平有限,不对之处还望矫正!         Java内存模型指定Java虚拟机如何与计算机的内存(RAM)一起工作.Java虚拟机是整个计算机的一个模型,所以这个模型自然包含了一个内存模型--也就是Java内存模型.         如果您想要设计正确的并发程序,那么理解Java内存模型是非常重要的.Java内存模型指定了不同线程如何以及何时可以看到由其他线程写入共享变量的值,以及在必要时如何同步访问共享变量

多线程并发之java内存模型JMM

多线程概念的引入是人类又一次有效压寨计算机的体现,而且这也是非常有必要的,因为一般运算过程中涉及到数据的读取,例如从磁盘.其他系统.数据库等,CPU的运算速度与数据读取速度有一个严重的不平衡,期间如果按一条线程执行将会在很多节点产生阻塞,使计算效率低下.另外,服务器端是java最擅长的领域,作为服务器必须要能同时响应多个客户端的请求,同样需要多线程的支持.在多线程情况下,高并发将带来数据的共享与竞争问题,tomcat作为中间件将多线程并发等细节尽量封装起来处理,使用户对多线程透明,更多地关注业务

在Java内存模型中测试并发程序代码_java

让我们来看看这段代码:   import java.util.BitSet; import java.util.concurrent.CountDownLatch; public class AnExample { public static void main(String[] args) throws Exception { BitSet bs = new BitSet(); CountDownLatch latch = new CountDownLatch(1); Thread t1 =

Java 并发/多线程教程(四)-并发模型

       本系列译自jakob jenkov的Java并发多线程教程(本章节部分内容参考http://ifeve.com/并发编程模型),个人觉得很有收获.由于个人水平有限,不对之处还望矫正!        并发系统可以有多种并发模型来实现,并发模型指定线程如何协同完成分配给他们的任务.不同的并发模型以不同的方式划分任务,并且线程与线程之间以不同的方式进行通信和协作. 并发模型与分布式系统的相似性          本文中描述的并发模型类似于分布式系统中使用的不同体系结构.在并发系统中,不同

Java并发开发:Lock框架详解

摘要: 我们已经知道,synchronized 是java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等.Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题.本文以synchronized与Lock的对比为切入点,对Java中的Lock框架的枝干部分进行了详细介绍,最后给出了锁的一些相关概念. 一. synchronized 的局限性

Java 并发/多线程教程(五)-相同线程

       本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获.由于个人水平有限,不对之处还望矫正!        相同线程是一并发框架模型,是一个单线程系统向外扩展成多个单线程的系统.这样的结果就是多个单线程并行运行. 为什么是单线程系统?         你也许会感到好奇,为什么当今还有人设计单线程系统.单线程系统之所以这么普及,是因为单线程系统相对于多线程并发系统更为简单.单线程系统不需要与其他线程共享任何数据.这就使得单线程系统可以使用非并发的数据结构,可以更

Java并发编程中的生产者与消费者模型简述_java

概述对于多线程程序来说,生产者和消费者模型是非常经典的模型.更加准确的说,应该叫"生产者-消费者-仓库模型".离开了仓库,生产者.消费者就缺少了共用的存储空间,也就不存在并非协作的问题了. 示例定义一个场景.一个仓库只允许存放10件商品,生产者每次可以向其中放入一个商品,消费者可以每次从其中取出一个商品.同时,需要注意以下4点: 1.  同一时间内只能有一个生产者生产,生产方法需要加锁synchronized. 2.  同一时间内只能有一个消费者消费,消费方法需要加锁synchroni

Java并发性和多线程介绍目录

Java并发性和多线程介绍 多线程的优点 多线程的代价 并发编程模型 如何创建并运行java线程 竞态条件与临界区 线程安全与共享资源 线程安全及不可变性 Java内存模型 JAVA同步块 线程通信 Java ThreadLocal Thread Signaling (未翻译) 死锁 避免死锁 饥饿和公平 嵌套管程锁死 Slipped Conditions Java中的锁 Java中的读/写锁 重入锁死 信号量 阻塞队列 线程池 CAS 剖析同步器 无阻塞算法 阿姆达尔定律 文章转自 并发编程网

Java 并发/多线程教程(十二)-JAVA同步块

本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获.由于个人水平有限,不对之处还望矫正! 一个Java同步块标记一个方法或一个代码块作为同步.可以使用Java同步块来避免竞态条件. java同步关键字       在Java中同步的块被标记为Synchronized关键字.Java中的同步块在某些对象上是同步的.在同一对象上同步的所有同步块只能在同一时间内执行一个线程.所有试图进入同步块的其他线程都被阻塞,直到同步块中的线程退出该块. Synchronized关键字可以