1. 程式人生 > >tomcat的執行緒模型

tomcat的執行緒模型

現在考慮這樣一個情景。你要做一個高效能的通訊程式,現在按照傳統方式開始做。客戶端就不寫了。伺服器端的程式碼大致如下:
//建立一個伺服器端socket
ServerSocket socket = new ServerSocket(port,backlog);
//開始監聽
while((Socket socket = socket.accept())!=null) {
//開一個執行緒,處理收到的socket。
Thread t = new BusinessThread(socket);
t.start();

//其他事情,例如計數,計算收到了多少個請求。
count++;
}

它的缺點是什麼:

開闢執行緒比較浪費時間和資源。
1、可能開闢到一定程度的時候,資源全被執行緒吃掉,導致效能下降。2、執行緒的數目不可控,因為你不知道這個執行緒什麼時候結束。

現在開始第一次改進。解決開闢執行緒浪費的時間和資源。最基本的思路,大家都應該能想到,我們用個執行緒池來做這個事情。假設執行緒池裡有了一些事先做好的執行緒。

//建立一個伺服器端socket
ServerSocket socket = new ServerSocket(port,backlog);
//開始監聽
while((Socket socket = socket.accept())!=null) {
//開一個執行緒,處理收到的socket。
ControlRunnable r = ThreadPool.getWorkThread();
if(r != null) {
ThreadPool.touch(r,socket); ==>>將該執行緒需要的資料繫結。
ThreadPool.runIt(r); ==>>執行完後需要在該執行緒中,將該執行緒放回去:Thread.release(r)。
} else {
wait();
//執行緒池中暫時沒有空閒執行緒可供使用。
}
}

ThreadPool.getWorkThread() {

if(totalThread - busyThread - 1 < 0)
return null; //沒有可用的執行緒了。
r = pool[totalThread - busyThread - 1];
pool[totalThread - busyThread - 1] = null;
busyThread++;
return r;

}

Thread.release(r) {
pool[totalThread - busyThread] = r;//注意這裡busyThread不可能為0
busyThread--;
}

這個程式的缺點是什麼?
getWorkThread()和r.release()需要同步。否則會發生混亂。這樣造成一個後果,就是由於getWorkThread在等待,導致很多socket連線被拒絕(超過了backlog)。

第二次改進:防止過多socket被拒絕。思路:保證連線能很快收到。我們可以在這裡放一個佇列,以便接收請求。
另外執行緒池中有很多執行緒在這裡監聽這個佇列,當這個佇列中有內容時,其中一個執行緒取出socket進行處理。

//建立一個伺服器端socket
ServerSocket socket = new ServerSocket(port,backlog);
//開始監聽
while((Socket socket = socket.accept())!=null) {
queue.putConnection(socket);
queue.notify();//通知執行緒池中的執行緒去處理。
}

執行緒中的處理過程:
while(true) {
Socket s = (Socket)queue.get();
if(s != null) {
//業務處理
} else {
wait();
}
}

putConnection的過程:
putConnection(s) {
if(queue.size>MAX_LENGTH)
write(s,"伺服器忙");
queue.put(s);
}

這個程式的缺點:1、queue在get的時候都需要同步。(注意put可以不同步,因為佇列是2頭的,我只需要同步取的那頭,放的那頭由於只有一個執行緒在放,因此可以不同步)
2、工作執行緒和主執行緒需要切換後才開始執行業務。
所以這個執行緒模型不是最優的。這個就是Master Slave Worker Thread 模型。

第三次改進:這就是Leader Follower Worker Thread。tomcat所用的執行緒模型。
思路:執行緒池中有很多執行緒處於等待工作狀態,只有一個執行緒的處於監聽狀態(這就是leader執行緒),接收到socket後就去處理(變成了follower執行緒),同時通知另一個執行緒進入監聽狀態。
//建立一個伺服器端socket

                leader執行緒                                              執行緒池中的某一空閒執行緒
//開始監聽
Socket socket = socket.accept()                                         wait();
ThreadPool.notify()  =====通知另一個執行緒進入接受狀態=========>          socket.accept();
//做業務處理                                                            //做業務處理
.....                                                                   .......
//業務處理完成                                                          //業務處理完成
returnControlToThreadPool();                                            returnControlToThreadPool();

注意這裡的leader執行緒,ThreadPool.notify()這句僅僅是發一個通知,然後進行業務處理,這樣在沒有執行緒切換的情況下就進行處理。
tomcat的原始碼這個地方是一個讀起來比較困難的地方,必須瞭解它的執行緒模型才能繼續讀下去。
tomcat這部分的相關原始碼參見
org.apache.tomcat.util.threads.ThreadPool(這是該執行緒模型中最重要的類,和它的內部類ControlRunnable)
org.apache.tomcat.util.threads.ThreadPoolRunnable
org.apache.tomcat.util.threads.ThreadWithAttributes(繫結執行緒的引數,類似ThreadLocal)
org.apache.coyote.http11.Http11BaseProtocol(主要是該類中的內部類Http11ConnectionHandler)
org.apache.tomcat.util.net.PoolTcpEndpoint

缺點:returnControlToThreadPool()時需要同步,但這是在業務已經處理完成的情況下。並且很難有大量執行緒同時做這個操作,可以忽略不計。

本文是原創,非是轉帖,歡迎高手指正,QQ群:8990357。我寫了一個該模型的簡單例子,不好上傳,只好作罷。