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

Java的多線程特性爲構建高性能的應用提供了極大的方便,但是也帶來了很多的麻煩。線程間同步、數據一致性等煩瑣的問題需要細心的考慮,一不當心就會出現一些微妙的,難以調試的錯誤。java

另外。應用邏輯和線程邏輯糾纏在一塊兒。會致使程序的邏輯結構混亂,難以複用和維護。設計模式

本文試圖給出一個解決問題的方案。經過構建一個併發模型框架(framework),使得開發多線程的應用變得easy。多線程

基礎知識

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

synchronized聲明只對於一些較爲簡單的線程間同步問題比較有效,對於哪些複雜的同步問題。比方帶有條件的同步問題,Java提供了另外的解決方法,wait/notify/notifyAll。得到對象監視器的線程可以經過調用該對象的wait方法主動釋放監視器,等待在該對象的線程等待隊列上,此時其它線程可以獲得監視器從而訪問該對象,以後可以經過調用notify/notifyAll方法來喚醒先前因調用wait方法而等待的線程。

普通狀況下,對於wait/notify/notifyAll方法的調用都是依據必定的條件來進行的。比方:經典的生產者/消費者問題中對於隊列空、滿的推斷。熟悉POSIX的讀者會發現,使用wait/notify/notifyAll可以很是easy的實現POSIX中的一個線程間的高級同步技術:條件變量。架構


簡單樣例

考慮一個簡單的樣例,咱們有一個服務提供者。它經過一個接口對外提供服務,服務內容很easy,就是在標準輸出上打印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的兩個版本號:多線程版本號和單線程版本號(有些地方,比方其它項目,可能沒有併發的問題)。easy帶來同步更新和正確選擇版本號的問題,給維護帶來麻煩。
  2. 假設多個併發的Client頻繁調用該服務。由於是直接同步調用,會形成Client堵塞,減小服務質量。
  3. 很是難進行一些靈活的控制,比方:依據Client的優先級進行排隊等等。

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

怎樣才幹較好的解決這些問題,有沒有一個可以重用的解決方式呢?讓咱們先把這些問題放一放,先來談談和框架有關的一些問題。



框架概述

熟悉面向對象的讀者必定知道面向對象的最大的優點之中的一個就是:軟件複用。經過複用,可以下降很是多的工做量。提升軟件開發生產率。複用自己也是分層次的。代碼級的複用和設計架構的複用。

那麼什麼是框架呢?所謂框架,它不一樣於通常的標準庫,是指一組緊密關聯的(類)classes,強調彼此的配合以完畢某種可以反覆運用的設計概念。這些類之間以特定的方式合做。彼此不可或缺。

它們至關程度的影響了你的程序的形貌。框架自己規劃了應用程序的骨幹,讓程序遵循必定的流程和動線。展示必定的風貌和功能。spa

這樣就使程序猿沒必要費力於通用性的功能的繁文縟節,集中精力於專業領域。
有一點必須要強調,放之四海而皆準的框架是不存在的。也是最沒實用處的。框架每每都是針對某個特定應用領域的。是在對這個應用領域進行深入理解的基礎上,抽象出該應用的概念模型。在這些抽象的概念上搭建的一個模型,是一個有形無體的框架。不一樣的詳細應用依據自身的特色對框架中的抽象概念進行實現,從而賦予框架生命。完畢應用的功能。
基於框架的應用都有兩部分構成:框架部分和特定應用部分。要想達到框架複用的目標,必須要作到框架部分和特定應用部分的隔離。線程

使用面向對象的一個強大功能:多態。可以實現這一點。在框架中完畢抽象概念之間的交互、關聯,把詳細的實現交給特定的應用來完畢。

當中通常都會大量使用了Template Method設計模式。


構建框架

怎樣構建一個Java併發模型框架呢?讓咱們先回到原來的問題,先來分析一下緣由。形成要維護多線程和單線程兩個版本號的緣由是由於把應用邏輯和併發邏輯混在一塊兒,假設能夠作到把應用邏輯和併發模型進行很是好的隔離。那麼應用邏輯自己就行很是好的被複用,而且也很是容易把併發邏輯加入進來而不會相應用邏輯形成不論什麼影響。形成Client堵塞。性能減小以及沒法進行額外的控制的緣由是由於所有的服務調用都是同步的,解決方式很是easy。改成異步調用方式,把服務的調用和服務的運行分離。
首先來介紹一個概念,活動對象(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也能夠獨立的進行重用。類結構圖例如如下:


讀者easy看出,使用框架也添加了一些複雜性。對於一些簡單的應用來講可能根本就沒有必要使用本框架。

但願讀者能夠依據本身的實際狀況進行推斷。


結論

本文環繞一個簡單的樣例論述了怎樣構架一個Java併發模型框架,當中使用了一些構建框架的常用技術。固然所構建的框架和一些成熟的商用框架相比,顯得很是稚嫩。比方沒有考慮服務調用有返回值的狀況,但是其思想方法是一致的,但願讀者可以深加領會,這樣無論對於構建本身的框架仍是理解一些其它的框架都是很是有幫助的。

讀者可以對本文中的框架進行擴充,直接應用到本身的工做中。參考文獻〔1〕中對於構建併發模型框架中的很是多細節問題進行了深刻的論述。有興趣的讀者可以自行研究。如下列出本框架的優缺點:
長處:
加強了應用的併發性,簡化了同步控制的複雜性
服務的請求和服務的運行分離,使得可以對服務請求排隊,進行靈活的控制
應用邏輯和併發模型分離,使得程序結構清晰。易於維護、重用
可以使開發人員集中精力於應用領域
缺點: 由於框架所需類的存在,在必定程度上添加了程序的複雜性 假設應用需要過多的活動對象,由於線程切換開銷會形成性能降低 可能會形成調試困難

相關文章
相關標籤/搜索