Tomcat中常見執行緒說明
本文講述了Tomcat的常見執行緒的功能、名稱、執行緒池和配置等資訊,其中原始碼來自於Tomcat 6.0.18。
Work執行緒
功能
HTTP請求的處理執行緒(非NIO)。當有新的http請求進來後,則會從執行緒池中獲得一個執行緒Work物件,呼叫Work.assign函式,將新到的http請求分配給這個執行緒。
名稱
名稱是http-[IpAddr]-[Port]-[Number],如http-0.0.0.0-8080-1
這個可以從Http11Protocol中的setName函式和Worker中的start方法得知這個命名方式。
1 | publicString |
2 | StringencodedAddr=""; |
3 | if(getAddress()!=null){ |
4 | encodedAddr=""+getAddress(); |
5 | if(encodedAddr.startsWith("/")) |
6 | encodedAddr=encodedAddr.substring(1); |
7 | encodedAddr=URLEncoder.encode(encodedAddr)+"-"; |
8 | } |
9 | |
10 | return("http- |
11 | } |
12 | |
13 |
執行緒類:JIoEndpoint.Work
在JIoEndpoint.Work的run方法中呼叫await方法等待並獲得下一個socket,傳給handle進行處理。在await方法中,如果沒有分配新的客戶端請求socket, available變數會一直false,並會迴圈呼叫wait方法阻塞自己,同時釋放Work物件的鎖,直到Acceptor執行緒獲得新的socket, 並呼叫Work.assign方法分配給該工作執行緒。 這時availble變數才為設定為true,並且await方法會返回分配的socket物件。
1 | protectedclassWorkerimplementsRunnable{ |
2 | |
3 | protectedThreadthread=null; |
4 | |
5 | protectedbooleanavailable=false; |
6 | |
7 | protectedSocketsocket=null; |
8 | |
9 | /** |
10 | |
11 | *ProcessanincomingTCP/IPconnectiononthespecifiedsocket.Any |
12 | |
13 | *exceptionthatoccursduringprocessingmustbeloggedandswallowed. |
14 | |
15 | *<b>NOTE</b>:ThismethodiscalledfromourConnector'sthread.We |
16 | |
17 | *mustassignittoourownthreadsothatmultiplesimultaneous |
18 | |
19 | *requestscanbehandled. |
20 | |
21 | * |
22 | |
23 | *@paramsocketTCPsockettoprocess |
24 | |
25 | */ |
26 | |
27 | synchronizedvoidassign(Socketsocket){ |
28 | |
29 | //WaitfortheProcessortogetthepreviousSocket |
30 | |
31 | while(available){ |
32 | |
33 | try{ |
34 | |
35 | wait(); |
36 | |
37 | }catch(InterruptedExceptione){ |
38 | |
39 | } |
40 | |
41 | } |
42 | |
43 | //StorethenewlyavailableSocketandnotifyourthread |
44 | |
45 | this.socket=socket; |
46 | |
47 | available=true; |
48 | |
49 | notifyAll(); |
50 | |
51 | } |
52 | |
53 | /** |
54 | |
55 | *等待新分配的Socket |
56 | |
57 | */ |
58 | |
59 | privatesynchronizedSocketawait(){ |
60 | |
61 | //等待Connector提供新的Socket |
62 | |
63 | while(!available){ |
64 | |
65 | try{ |
66 | |
67 | wait(); |
68 | |
69 | }catch(InterruptedExceptione){ |
70 | |
71 | } |
72 | |
73 | } |
74 | |
75 | //通知Connector我們已經接收到這個Socket |
76 | |
77 | Socketsocket=this.socket; |
78 | |
79 | available=false; |
80 | |
81 | notifyAll(); |
82 | |
83 | return(socket); |
84 | |
85 | } |
86 | |
87 | /** |
88 | |
89 | *後臺執行緒,監聽進入的TCP/IP連線,並傳遞給合適的處理模組 |
90 | |
91 | */ |
92 | |
93 | publicvoidrun(){ |
94 | |
95 | //Processrequestsuntilwereceiveashutdownsignal |
96 | |
97 | //處理請求直到我們接收到shutdown訊號 |
98 | |
99 | while(running){ |
100 | |
101 | //等待下一個分配的socket |
102 | |
103 | Socketsocket=await(); |
104 | |
105 | if(socket==null) |
106 | |
107 | continue; |
108 | |
109 | //設定socket的選項,並處理socket |
110 | |
111 | if(!setSocketOptions(socket)||!handler.process(socket)){ |
112 | |
113 | //關閉socket |
114 | |
115 | try{ |
116 | |
117 | socket.close(); |
118 | |
119 | }catch(IOExceptione){ |
120 | |
121 | } |
122 | |
123 | } |
124 | |
125 | //Finishupthisrequest |
126 | |
127 | socket=null; |
128 | |
129 | //回收執行緒 |
130 | |
131 | recycleWorkerThread(this); |
132 | |
133 | } |
134 | |
135 | } |
136 | |
137 | /** |
138 | |
139 | *開啟後臺處理執行緒 |
140 | |
141 | */ |
142 | |
143 | publicvoidstart(){ |
144 | |
145 | thread=newThread(this); |
146 | |
147 | thread.setName(getName()+"-"+(++curThreads)); |
148 | |
149 | thread.setDaemon(true); |
150 | |
151 | thread.start(); |
152 | |
153 | } |
154 | |
155 | } |
156 | |
157 |
所屬執行緒池
所屬執行緒池實現功能比較簡單,是內嵌到JIoEndpoint類中的實現。基本資料結構是一個工作執行緒棧JIoEndpoint.WorkerStack。
執行緒池主要屬性
curThreadsBusy:當前繁忙執行緒數
curThreads:當前工作執行緒數
maxThreads:最大工作執行緒數
執行緒池啟動
這個執行緒池實現功能比較簡單,不需要太多啟動功能。可以從JIoEndpoint類的start方法看到,啟動初始化需要做的事是分配執行緒棧worker空間。
任務分配時序圖
任務分配
通過JIoEndPoint中createWorkerThread方法獲得一個工作執行緒。如在工作執行緒棧workers中獲得一個執行緒物件,如果執行緒棧已經是空的,並且當前執行緒數量curThreads還小於最大執行緒數maxThreads,那麼就建立一個新的工作執行緒。然後呼叫Work.assign方法分配給工作執行緒。
1 | protectedWorkercreateWorkerThread(){ |
2 | |
3 | //獲得工作執行緒棧workers的鎖 |
4 | |
5 | synchronized(workers){ |
6 | |
7 | //如果工作執行緒棧裡有執行緒則返回棧頂工作執行緒 |
8 | |
9 | if(workers.size()>0){ |
10 | |
11 | curThreadsBusy++; |
12 | |
13 | returnworkers.pop(); |
14 | |
15 | } |
16 | |
17 | //如果工作執行緒棧裡沒有執行緒,maxThreads大於0且當前執行緒數小於最大執行緒數,則建立一個新的執行緒 |
18 | |
19 | if((maxThreads>0)&&(curThreads<maxThreads)){ |
20 | |
21 | curThreadsBusy++; |
22 | |
23 | return(newWorkerThread()); |
24 | |
25 | }else{ |
26 | |
27 | //如果maxThreads小於0,則說明沒有限制,建立新的執行緒 |
28 | |
29 | if(maxThreads<0){ |
30 | |
31 | curThreadsBusy++; |
32 | |
33 | return(newWorkerThread()); |
34 | |
35 | }else{ |
36 | |
37 | return(null); |
38 | |
39 | } |
40 | |
41 | } |
42 | |
43 | } |
44 | |
45 | } |
46 | |
47 |
工作執行緒回收
JIoEndPoint中recycleWorkerThread方法是回收工作執行緒,當http請求處理完成,則呼叫該方法回收工作執行緒。該方法首先獲得worker物件鎖,然後呼叫workers.push方法將工作執行緒壓入工作執行緒棧中,接著將當前繁忙執行緒數減1,最後呼叫workers.notify方法。
1 | protectedvoidrecycleWorkerThread(WorkerworkerThread){ |
2 | |
3 | synchronized(workers){ |
4 | |
5 | workers.push(workerThread); |
6 | |
7 | curThreadsBusy--; |
8 | |
9 | workers.notify(); |
10 | |
11 | } |
12 | } |
配置
在Tomcat中配置檔案Server.xml中的Connector屬性配置最大執行緒數maxThreads。
例如:
<Connector port="8080"
maxThreads="150"
……/>
Acceptor執行緒
功能
獲得HTTP請求socket。並從工作執行緒池中獲得一個執行緒,將socket分配給一個工作執行緒。
名稱
http-[IPAddr]-[Port]-Acceptor-[Number],如http-0.0.0.0-8080-Acceptor-1
執行緒類:JIoEndpoint.Acceptor
所屬執行緒池
無
啟動時序圖
在啟動時會開啟Accepter執行緒,時序圖如下:
執行緒啟動
如上時序圖,在Tomcat啟動過程會呼叫JIoEndpoint類的start方法,會建立並啟動acceptorThreadCount個Acceptor執行緒。
1 | publicvoidstart()throwsException{ |
2 | |
3 | //Initializesocketifnotdonebefore |
4 | |
5 | if(!initialized){ |
6 | |
7 | init(); |
8 | |
9 | } |
10 | |
11 | if(!running){ |
12 | |
13 | running=true; |
14 | |
15 | paused=false; |
16 | |
17 | //如果沒有配置executor執行緒池,則建立工作執行緒棧worker,就是上例中的執行緒池的工作執行緒棧。 |
18 | |
19 | if(executor==null){ |
20 | |
21 | workers=newWorkerStack(maxThreads); |
22 | |
23 | } |
24 | |
25 | //啟動acceptor執行緒 |
26 | |
27 | for(inti=0;i<acceptorThreadCount;i++){ |
28 | |
29 | ThreadacceptorThread=newThread(newAcceptor(),getName()+"-Acceptor-"+i); |
30 | |
31 | acceptorThread.setPriority(threadPriority); |
32 | |
33 | acceptorThread.setDaemon(daemon); |
34 | |
35 | acceptorThread.start(); |
36 | |
37 | } |
38 | |
39 | } |
40 | |
41 | } |
屬性
acceptorThreadCount:開啟的acceptor執行緒數,從原始碼看到這個值並沒有通過配置設定,而是固定的值為1
配置
無
Main主執行緒
功能
完成裝配、初始化和啟動,之後會開啟SocketServer,並迴圈等待命令,如shutdown。
名稱:Main
執行緒類:Main主執行緒
所屬執行緒池:
無
catalina-exec執行緒
功能
StandardThreadExecutor的工作執行緒,功能和Work執行緒類似。如果為Connector配置了Executor,則會使用該執行緒處理http請求。
執行緒類:ThreadPoolExecutor.Work
所屬執行緒池:StandardThreadExecutor
類名是org.apache.catalina.core.StandardThreadExecutor,該執行緒池類通過代理設計模式對Java Concurrent包中的執行緒池ThreadPoolExecutor進行簡單的封裝。並實現了Lifecycle介面,以及增加了傳送訊息的功能。
屬性
minSpareThreads:最小空閒執行緒數
maxThreads:最大執行緒數
maxIdleTime:最大空閒時間
配置
在Server.xml檔案中配置Executor節點,支援如下屬性,
Name |
Executor的名稱 |
namePrefix |
工作執行緒字首 |
maxThreads |
最大執行緒數 |
minSpareThreads |
最小空閒執行緒數 |
maxIdleTime |
最大空閒時間 |
並在Connector節點配置executor,並指定為Executor的名稱。
例如:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4" maxIdleTime="200"/>
<Connector Address="0.0.0.0" port="8080" protocol="HTTP/1.1"executor="tomcatThreadPool".../>
TP-Processor執行緒
功能
AJP協議中Servlet容器的處理執行緒
名稱
TP-Processor-[Number],例如TP-Processor-1
執行緒類:ThreadPool.ControlRunnable
所屬執行緒池:org.apache.tomcat.util.threads.ThreadPool
該執行緒池還會啟動一個TP-Monitor執行緒監控空閒執行緒。在TheadPool會有一個ControlRunnable陣列儲存執行緒池中的工作執行緒。使用該執行緒池需要先呼叫start方法,進行ControlRunnable陣列初始化,minSpareThreads個空閒執行緒的建立,以及TP-Monitor執行緒的啟動。
屬性
maxThreads:最大執行緒數
minSpareThreads:最小空閒執行緒數
maxSpareThreads: 最大空閒執行緒數
執行緒池的啟動
通過ThreadPool.start方法,該方法會分配執行緒陣列pool,並開啟minSpareThreads空執行緒。如果最大空閒執行緒數小於最大執行緒數,則啟動TP-Monitor執行緒。
1 | publicsynchronizedvoidstart(){ |
2 | |
3 | stopThePool=false; |
4 | |
5 | currentThreadCount=0; |
6 | |
7 | currentThreadsBusy=0; |
8 | |
9 | adjustLimits(); |
10 | |
11 | pool=newControlRunnable[maxThreads]; |
12 | |
13 | //啟動minSpareThreads空閒執行緒 |
14 | |
15 | openThreads(minSpareThreads); |
16 | |
17 | //如果最大空閒執行緒數小於最大執行緒數,則啟動TP-Monitor執行緒 |
18 | |
19 | if(maxSpareThreads<maxThreads){ |
20 | |
21 | monitor=newMonitorRunnable(this); |
22 | |
23 | } |
24 | |
25 | } |
任務分配
使用ThreadPool.runIt來執行新的任務,在該方法中,會呼叫findControlRunnable方法來獲得一個工作執行緒。需要注意的是呼叫方不需要呼叫額外的方法來回收執行緒。當ControlRunnable執行緒完成指定的任務會自動將執行緒回收到執行緒池中。
findControlRunnable是ThreadPool執行緒池的關鍵方法,它提供了從執行緒池中獲得一個工作執行緒,並將相應的計數調整,如 tpOpen,currentThreadsBusy。
1 | /** |
2 | |
3 | *ExecutesagivenRunnableonathreadinthepool,blockifneeded. |
4 | |
5 | */ |
6 | |
7 | publicvoidrunIt(ThreadPoolRunnabler){ |
8 | |
9 | if(null==r){ |
10 | |
11 | thrownewNullPointerException(); |
12 | |
13 | } |
14 | |
15 | //從執行緒池中獲得一個工作執行緒 |
16 | |
17 | ControlRunnablec=findControlRunnable(); |
18 | |
19 | //執行任務 |
20 | |
21 | c.runIt(r); |
22 | |
2 |