轉自:http://www.ibm.com/developerworks/cn/java/l-multithreading/ 並增加自己的分析,希望對並發系統的設計提供一種思路。
首先舉一個簡單的例子系統中有一個服務提供者,他通過介面對外提供服務,比如列印hello world。
//定義介面public interface Service { public void sayHello();}//介面實現public class ServiceImp implements Service { public void sayHello() { System.out.println("Hello World!"); }}//介面調用者public class Client { public Client(Service s) { _service = s; } public void requestService() { _service.sayHello(); } private Service _service;}//主程式public class Main { public static void main(String[] args) { /* 並發邏輯增加前,對於sayHello服務的調用方法 */ Service s = new ServiceImp(); Client c = new Client(s); c.requestService(); }}
如果現在有新的需求,要求該服務必須支援Client的並發訪問。一種簡單的方法就是在原來的介面上加上synchronized 聲明,但是帶來的效能損失相當大,這種方案基本被斃掉(當然對於本例來說,目前是沒有必要的,因為ServiceImp沒有需要保護的資料,但是隨著需求的變化,以後可能會有的)。而且這樣的話就要在原來的代碼上改,成本較大。而且並發邏輯和應用邏輯耦合在一起,對代碼的可讀性和可維護性都是極大的損害。如果能做到將並發邏輯和應用邏輯分離開來,那麼對系統的侵入性將大大降低,應用邏輯的自身可以被很好的複用。造成Client阻塞,效能降低以及無法滿足並發的很大原因是所有的服務調用都是同步的。現在看下改為非同步情況。
核心就是使用主動對象來封裝並發邏輯,然後把Client的請求轉寄給實際的服務提供者(應用邏輯),這樣無論是Client還是實際的服務提供者都不用關心並發的存在,不用考慮並發所帶來的資料一致性問題。從而實現應用邏輯和並發邏輯的隔離,服務調用和服務執行的隔離。下面給出關鍵的實現細節。
本架構有如下幾部分構成:
- 一個ActiveObject類,從Thread繼承,封裝了並發邏輯的使用中的物件
- 一個ActiveQueue類,主要用來存放調用者請求
- 一個MethodRequest介面,主要用來封裝調用者的請求,Command設計模式的一種實現方式
/** * 封裝調用者的請求,就是封裝了一個對外的介面 */public interface MethodRequest { public void call();}/***用來存放調用者請求,就是一個並發棧的實現*/public 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 { System.out.println("開始嘗試擷取隊列"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } mr = (MethodRequest) _queue.pop(); notifyAll(); return mr; } private Stack _queue; private final static int QUEUE_SIZE = 20;}/** * 線程對象,封裝了非同步並發邏輯 */public 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 介面的形式,另外ActiveQueue 的實現有點低效,可以用java並發包裡的實現。對於這個例子,首先要把實際的邏輯封裝成MethodRequest
public class SayHello implements MethodRequest { public SayHello(Service s) { _service = s; } public void call() { _service.sayHello(); } private Service _service;}
接下來當然是需要將請求的封裝、排隊,執行封裝好對外就是統一入口,為了做到對Client透明,該類必須實現Service介面。定義如下:
public 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;}
其他的邏輯代碼都沒有變,可以看下修改前後的調用
public class Main { public static void main(String[] args) { /* 並發邏輯增加前,對於sayHello服務的調用方法 */// Service s = new ServiceImp();// Client c = new Client(s);// c.requestService(); /* 並發邏輯增加後,對於sayHello服務的調用方法 */ Service s = new ServiceProxy(); Client c = new Client(s); c.requestService(); }}
可以看到應用邏輯的實現Service的實現ServiceImp並沒有改變,他只是單純的實現了應用的邏輯,沒有關心是否需要並發。當然這個例子只是舉了一個很簡單的情境,只需要調用不需要傳回值,但是在很多情境中調用是需要傳回值的,這個值得再思考下。