構建本身的Java併發模型框架

Java的多線程特性爲構建高性能的應用提供了極大的方便,可是也帶來了很多的麻煩。線程間同步、數據一致性等煩瑣的問題須要細心的考慮,一不當心就會出現一些微妙的,難以調試的錯誤。另外,應用邏輯和線程邏輯糾纏在一塊兒,會致使程序的邏輯結構混亂,難以複用和維護。本文試圖給出一個解決這個問題的方案,經過構建一個併發模型框架(framework),使得開發多線程的應用變得容易。java

基礎知識

Java中內置了對於對象併發訪問的支持,每個對象都有一個監視器(monitor),同時只容許一個線程持有監視器從而進行對對象的訪問,那些沒有得到監視器的線程必須等待直到持有監視器的線程釋放監視器。對象經過synchronized關鍵字來聲明線程必須得到監視器才能進行對本身的訪問。

synchronized聲明僅僅對於一些較爲簡單的線程間同步問題比較有效,對於哪些複雜的同步問題,好比帶有條件的同步問題,Java提供了另外的解決方法,wait/notify/notifyAll。得到對象監視器的線程能夠經過調用該對象的wait方法主動釋放監視器,等待在該對象的線程等待隊列上,此時其餘線程能夠獲得監視器從而訪問該對象,以後能夠經過調用notify/notifyAll方法來喚醒先前因調用wait方法而等待的線程。通常狀況下,對於wait/notify/notifyAll方法的調用都是根據必定的條件來進行的,好比:經典的生產者/消費者問題中對於隊列空、滿的判斷。熟悉POSIX的讀者會發現,使用wait/notify/notifyAll能夠很容易的實現POSIX中的一個線程間的高級同步技術:條件變量。

簡單例子

考慮一個簡單的例子,咱們有一個服務提供者,它經過一個接口對外提供服務,服務內容很是簡單,就是在標準輸出上打印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的優先級進行排隊等等。

這些問題對於大型的多線程應用服務器尤其突出,對於一些簡單的應用(如本文中的例子)可能根本不用考慮。本文正是要討論這些問題的解決方案,文中的簡單的例子只是提供了一個說明問題,展現思路、方法的平臺。設計模式

如何才能較好的解決這些問題,有沒有一個能夠重用的解決方案呢?讓咱們先把這些問題放一放,先來談談和框架有關的一些問題。

框架概述

熟悉面向對象的讀者必定知道面向對象的最大的優點之一就是:軟件複用。經過複用,能夠減小不少的工做量,提升軟件開發生產率。複用自己也是分層次的,代碼級的複用和設計架構的複用。

那麼什麼是框架呢?所謂框架,它不一樣於通常的標準庫,是指一組緊密關聯的(類)classes,強調彼此的配合以完成某種能夠重複運用的設計概念。這些類之間以特定的方式合做,彼此不可或缺。它們至關程度的影響了你的程序的形貌。框架自己規劃了應用程序的骨幹,讓程序遵循必定的流程和動線,展示必定的風貌和功能。這樣就使程序員沒必要費力於通用性的功能的繁文縟節,集中精力於專業領域。
有一點必需要強調,放之四海而皆準的框架是不存在的,也是最沒有用處的。框架每每都是針對某個特定應用領域的,是在對這個應用領域進行深入理解的基礎上,抽象出該應用的概念模型,在這些抽象的概念上搭建的一個模型,是一個有形無體的框架。不一樣的具體應用根據自身的特色對框架中的抽象概念進行實現,從而賦予框架生命,完成應用的功能。
基於框架的應用都有兩部分構成:框架部分和特定應用部分。要想達到框架複用的目標,必需要作到框架部分和特定應用部分的隔離。使用面向對象的一個強大功能:多態,能夠實現這一點。在框架中完成抽象概念之間的交互、關聯,把具體的實現交給特定的應用來完成。其中通常都會大量使用了Template Method設計模式。

構建框架

如何構建一個Java併發模型框架呢?讓咱們先回到原來的問題,先來分析一下緣由。形成要維護多線程和單線程兩個版本的緣由是因爲把應用邏輯和併發邏輯混在一塊兒,若是可以作到把應用邏輯和併發模型進行很好的隔離,那麼應用邏輯自己就能夠很好的被複用,並且也很容易把併發邏輯添加進來而不會對應用邏輯形成任何影響。形成Client阻塞,性能下降以及沒法進行額外的控制的緣由是因爲全部的服務調用都是同步的,解決方案很簡單,改成異步調用方式,把服務的調用和服務的執行分離。
首先來介紹一個概念,活動對象(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也可以獨立的進行重用。類結構圖以下:


讀者容易看出,使用框架也增長了一些複雜性,對於一些簡單的應用來講可能根本就沒有必要使用本框架。但願讀者可以根據本身的實際狀況進行判斷。

結論

本文圍繞一個簡單的例子論述瞭如何構架一個Java併發模型框架,其中使用了一些構建框架的經常使用技術,固然所構建的框架和一些成熟的商用框架相比,顯得很是稚嫩,好比沒有考慮服務調用有返回值的狀況,可是其思想方法是一致的,但願讀者可以深加領會,這樣不管對於構建本身的框架仍是理解一些其餘的框架都是頗有幫助的。讀者能夠對本文中的框架進行擴充,直接應用到本身的工做中。參考文獻〔1〕中對於構建併發模型框架中的不少細節問題進行了深刻的論述,有興趣的讀者能夠自行研究。下面列出本框架的優缺點:
優勢:
加強了應用的併發性,簡化了同步控制的複雜性
服務的請求和服務的執行分離,使得能夠對服務請求排隊,進行靈活的控制
應用邏輯和併發模型分離,使得程序結構清晰,易於維護、重用
可使開發者集中精力於應用領域
缺點: 因爲框架所需類的存在,在必定程度上增長了程序的複雜性 若是應用須要過多的活動對象,因爲線程切換開銷會形成性能降低 可能會形成調試困難
相關文章
相關標籤/搜索