轉自: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並沒有改變,他只是單純的實現了應用的邏輯,沒有關心是否需要併發。當然這個例子只是舉了一個很簡單的場景,只需要調用不需要返回值,但是在很多場景中調用是需要返回值的,這個值得再思考下。