jsp 多線程開發與Java並行存取模型架構

來源:互聯網
上載者:User

基礎知識

  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。類結構圖如下:

  代碼如下:

  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並行存取模型架構,其中使用了一些構建架構的常用技術,當然所構建的架構和一些成熟的商用架構相比,顯得非常稚嫩,比如沒有考慮服務調用有傳回值的情況,但是其思想方法是一致的,希望讀者能夠深加領會,這樣無論對於構建自己的架構還是理解一些其他的架構都是很有協助的。讀者可以對本文中的架構進行擴充,直接應用到自己的工作中。

  

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.