如何構建一個Java併發模型框架呢?讓我們先回到原來的問題,先來分析一下原因。造成要維護多線程和單線程兩個版本的原因是由於把應用邏輯和併發邏輯混在一起,如果能夠做到把應用邏輯和併發模型進行很好的隔離,那麼應用邏輯本身就可以很好的被複用,而且也很容易把併發邏輯添加進來而不會對應用邏輯造成任何影響。造成Client阻塞,性能降低以及無法進行額外的控制的原因是由於所有的服務調用都是同步的,解決方案很簡單,改爲異步調用方式,把服務的調用和服務的執行分離。
首先來介紹一個概念,活動對象(Active Object)。所謂活動對象是相對於被動對象(passive object)而言的,被動對象的方法的調用和執行都是在同一個線程中的,被動對象方法的調用是同步的、阻塞的,一般的對象都屬於被動對象;主動對象的方法的調用和執行是分離的,主動對象有自己獨立的執行線程,主動對象的方法的調用是由其他線程發起的,但是方法是在自己的線程中執行的,主動對象方法的調用是異步的,非阻塞的。
本框架的核心就是使用主動對象來封裝併發邏輯,然後把Client的請求轉發給實際的服務提供者(應用邏輯),這樣無論是Client還是實際的服務提供者都不用關心併發的存在,不用考慮併發所帶來的數據一致性問題。從而實現應用邏輯和併發邏輯的隔離,服務調用和服務執行的隔離。下面給出關鍵的實現細節。
本框架有如下幾部分構成:
一個ActiveObject類,從Thread繼承,封裝了併發邏輯的活動對象
一個ActiveQueue類,主要用來存放調用者請求
一個MethodRequest接口,主要用來封裝調用者的請求,Command設計模式的一種實現方式
它們的一個簡單的實現如下:
package multithreading.module;
/**
*主要用來封裝調用者的請求,Command設計模式的一種實現方式
*/
public interface MethodRequest {
public void call();
}
package multithreading.module;
import java.util.Stack;
/**
*用來存放調用者的請求, 其實就是一個producer/consumer隊列
*/
public class ActiveQueue {
private Stack<MethodRequest> _queue;
private final static int QUEUE_SIZE = 20;
public ActiveQueue(){
_queue = new Stack<MethodRequest>();
}
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 = _queue.pop();
notifyAll();
return mr;
}
}
package multithreading.module;
/**
*封裝併發邏輯的活動對象
*/
public class ActiveObject extends Thread {
private ActiveQueue _queue;
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();
}
}
}
通過上面的代碼可以看出正是這些類相互合作完成了對併發邏輯的封裝。開發者只需要根據需要實現MethodRequest接口,另外再定義一個服務代理類提供給使用者,在服務代理者類中把服務調用者的請求轉化爲MethodRequest實現,交給活動對象即可。
使用該框架,可以較好的做到應用邏輯和併發模型的分離,從而使開發者集中精力於應用領域,然後平滑的和併發模型結合起來,並且可以針對ActiveQueue定製排隊機制,比如基於優先級等。
基於框架的解決方案
使用上述的框架重新實現前面的例子,提供對於併發的支持。第一步先完成對於MethodRequest的實現,對於我們的例子來說實現如下:
package multithreading.module;
public class SayHello implements MethodRequest {
private Service _service;
public SayHello(Service s){
_service = s;
}
@Override
public void call() {
_service.sayHello();
}
}
該類完成了對於服務提供接口sayHello方法的封裝。接下來定義一個服務代理類,來完成請求的封裝、排隊功能,當然爲了做到對Client透明,該類必須實現Service接口。定義如下:
package multithreading.module;
public class ServiceProxy implements Service {
private Service _service;
private ActiveObject _active_object;
public ServiceProxy(){
_service = new ServiceImp();
_active_object = new ActiveObject();
}
@Override
public void sayHello() {
MethodRequest mr = new SayHello(_service);
_active_object.enqueue(mr);
}
}
其他的類和接口定義不變,下面對比一下併發邏輯增加前後的服務調用的變化,併發邏輯增加前,對於sayHello服務的調用方法爲註釋部分;併發邏輯增加後,對於sayHello服務的調用方法爲未註釋部分。
package multithreading.module;
public class TestMain {
public static void main(String[] args) {
//單線程版本
/*Service service = new ServiceImp();
Client client = new Client(service);
client.requestService();*/
//多線程版本
Service service = new ServiceProxy();
Client client = new Client(service);
client.requestService();
}
}
可以看出併發邏輯增加前後對於Client的ServiceImp都無需作任何改變,使用方式也非常一致,ServiceImp也能夠獨立的進行重用。類結構圖如下: