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方法得知這個命名方式。

public String getName() {
   String encodedAddr = "";
   if (getAddress() != null) {
       encodedAddr = "" + getAddress();
       if (encodedAddr.startsWith("/" ))
           encodedAddr = encodedAddr.substring(1);
       encodedAddr = URLEncoder. encode(encodedAddr) + "-";
   }
 
10     return ("http-" + encodedAddr + endpoint.getPort());
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對象。

protected class Worker implements Runnable {
 
    protected Thread thread = null;
 
    protected boolean available = false;
 
    protected Socket socket = null;
 
    /**
10   
11           * Process an incoming TCP/IP connection on the specified socket.  Any
12   
13           * exception that occurs during processing must be logged and swallowed.
14   
15           * <b>NOTE</b> :  This method is called from our Connector's thread.  We
16   
17           * must assign it to our own thread so that multiple simultaneous
18   
19           * requests can be handled.
20   
21           *
22   
23           * @param socket TCP socket to process
24   
25           */
26   
27      synchronized void assign(Socket socket ) {
28   
29          // Wait for the Processor to get the previous Socket
30   
31          while (available ) {
32   
33              try {
34   
35                      wait();
36   
37              } catch (InterruptedException e) {
38   
39              }
40   
41          }
42   
43          // Store the newly available Socket and notify our thread
44   
45          this.socket = socket ;
46   
47          available = true ;
48   
49          notifyAll();
50   
51      }
52   
53      /**
54   
55       * 等待新分配的Socket
56   
57       */
58   
59      private synchronized Socket await() {
60   
61          //等待Connector提供新的Socket
62   
63          while (!available ) {
64   
65              try {
66   
67                      wait();
68   
69              } catch (InterruptedException e) {
70   
71              }
72   
73          }
74   
75          //通知Connector我們已經接收到這個Socket
76   
77          Socket socket = 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      public void run() {
94   
95          // Process requests until we receive a shutdown signal
96   
97          //處理請求直到我們接收到shutdown信號
98   
99          while (running ) {
100   
101                  //等待下一個分配的socket
102   
103              Socket socket = 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 (IOException e) {
120   
121                  }
122   
123              }
124   
125              // Finish up this request
126   
127              socket = null;
128   
129              //回收線程
130   
131              recycleWorkerThread( this);
132   
133          }
134   
135      }
136   
137      /**
138   
139       * 開啓後臺處理線程
140   
141       */
142   
143      public void start() {
144   
145          thread = new Thread(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空間。

任務分配時序圖

1

任務分配

通過JIoEndPoint中createWorkerThread方法獲得一個工作線程。如在工作線程棧workers中獲得一個線程對象,如果線程棧已經是空的,並且當前線程數量curThreads還小於最大線程數maxThreads,那麼就創建一個新的工作線程。然後調用Work.assign方法分配給工作線程。

protected Worker createWorkerThread() {
 
    //獲得工作線程棧workers的鎖
 
    synchronized (workers ) {
 
        //如果工作線程棧裏有線程則返回棧頂工作線程
 
        if (workers .size() > 0) {
10   
11              curThreadsBusy++;
12   
13              return workers .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方法。

protected void recycleWorkerThread(Worker workerThread) {
 
    synchronized (workers ) {
 
        workers.push(workerThread);
 
        curThreadsBusy--;
 
        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線程,時序圖如下:

2

線程啓動

如上時序圖,在Tomcat啓動過程會調用JIoEndpoint類的start方法,會創建並啓動acceptorThreadCount個Acceptor線程。

public void start() throws Exception {
 
    // Initialize socket if not done before
 
    if (!initialized ) {
 
        init();
 
    }
10   
11      if (!running ) {
12   
13          running = true ;
14   
15          paused = false ;
16   
17          //如果沒有配置executor線程池,則創建工作線程棧worker, 就是上例中的線程池的工作線程棧。
18   
19          if (executor == null) {
20   
21              workers = new WorkerStack(maxThreads);
22   
23          }
24   
25          //啓動acceptor線程
26   
27          for (int i = 0; i < acceptorThreadCount; i++) {
28   
29               Thread acceptorThread = new Thread(new Acceptor(), 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線程。

public synchronized void start() {
 
    stopThePool=false ;
 
    currentThreadCount  = 0;
 
    currentThreadsBusy  = 0;
 
    adjustLimits();
10   
11      pool = new ControlRunnable[maxThreads];
12   
13      //啓動minSpareThreads空閒線程
14   
15      openThreads( minSpareThreads);
16   
17      //如果最大空閒線程數小於最大線程數,則啓動TP-Monitor線程
18   
19      if (maxSpareThreads < maxThreads) {
20   
21          monitor = new MonitorRunnable(this);
22   
23      }
24      
25  }

任務分配

使用ThreadPool.runIt來運行新的任務,在該方法中,會調用findControlRunnable方法來獲得一個工作線程。需要注意的是調用方不需要調用額外的方法來回收線程。當ControlRunnable線程完成指定的任務會自動將線程回收到線程池中。

findControlRunnable是ThreadPool線程池的關鍵方法,它提供了從線程池中獲得一個工作線程,並將相應的計數調整,如 tpOpen,currentThreadsBusy。

/**
 
 * Executes a given Runnable on a thread in the pool, block if needed.
 
 */
 
public void runIt(ThreadPoolRunnable r) {
 
    if(null == r) {
10   
11          throw new NullPointerException();
12   
13      }
14   
15      //從線程池中獲得一個工作線程
16   
17      ControlRunnable c = findControlRunnable();
18   
19      //運行任務
20   
21      c.runIt(r);
22   
23  }
24   
25  private ControlRunnable findControlRunnable() {
26   
27      ControlRunnable c= null;
28   
29      if ( stopThePool ) {
30   
31          throw new IllegalStateException();
32   
33      }
34   
35      //從線程池中獲得一個空閒線程
36   
37      synchronized(this ) {
38   
39          //當前繁忙線程和當前線程數相同,則表示所有的開啓線程都是繁忙的。
40   
41          while (currentThreadsBusy == currentThreadCount) {
42   
43              //如果當前線程數比最大線程數小
44   
45              if (currentThreadCount < maxThreads) {
46   
47                  // Not all threads were open,
48   
49                  // Open new threads up to the max number of idel threads
50   
51   
52                  int toOpen = currentThreadCount + minSpareThreads;
53   
54                  openThreads(toOpen);
55   
56              } else {
57   
58                  logFull(log, currentThreadCount, maxThreads );
59   
60                  //線程數已經滿了,等待線程成爲空閒線程
61   
62                  try {
63   
64                     this.wait();
65   
66                  }
67   
68                  // was just catch Throwable -- but no other
69   
70                  // exceptions can be thrown by wait, right?
71   
72                  // So we catch and ignore this one, since
73   
74                  // it'll never actually happen, since nowhere
75   
76                  // do we say pool.interrupt().
77   
78                  catch(InterruptedException e) {
79   
80                     log.error("Unexpected exception" , e);
81   
82                  }
83   
84                  if( log .isDebugEnabled() ) {
85   
86                     log.debug("Finished waiting: CTC=" +currentThreadCount +
87   
88                     ", CTB=" + currentThreadsBusy );
89   
90                  }
91   
92                  // Pool was stopped. Get away of the pool.
93   
94                  if( stopThePool ) {
95   
96                     break;
97   
98                  }
99   
100              }
101   
102         }
103   
104         //線程池已經關閉,離開線程池
105   
106         if(0 == currentThreadCount || stopThePool) {
107   
108                throw new IllegalStateException();
109   
110          }
111   
112          //到了這裏,表示有空閒線程可用
113   
114          //取出數組pool中最後一個線程
115   
116          int pos = currentThreadCount - currentThreadsBusy - 1;
117   
118          c = pool[pos];
119   
120          pool[pos] = null;
121   
122          //繁忙線程數加1
123   
124          currentThreadsBusy++;
125   
126      }
127   
128      return c;
129   
130  }
131   
132  /** 
133   
134   *開啓線程
135   
136   * @param toOpen 我們將要開啓的線程數
137   
138   */
139   
140  protected void openThreads(int toOpen) {
141   
142      if(toOpen > maxThreads ) {
143   
144           toOpen = maxThreads;
145   
146      }
147   
148      //創建空閒線程
149   
150      for(int i = currentThreadCount ; i < toOpen ; i++) {
151   
152          //需要減去currentThreadsBusy, 因爲繁忙線程已經從pool數組中移出
153   
154          pool[i - currentThreadsBusy ] = new ControlRunnable( this);
155   
156      }
157   
158      currentThreadCount = toOpen;
159   
160  }

 

工作線程回收

通過ThreadPool.returnController方法回收線程。該方法會將繁忙線程數currentThreadsBusy減1,並將線程回收到線程數組中。

/**
 
 * 將線程返還線程池
 
 */
protected synchronized void returnController (ControlRunnable c) {
 
    if(0 == currentThreadCount || stopThePool) {
 
10          c.terminate();
11   
12          return;
13   
14      }
15   
16      // atomic
17   
18      currentThreadsBusy--;
19   
20      //將線程回收到pool數組中
21   
22      pool[currentThreadCount - currentThreadsBusy - 1] = c;
23   
24      //notify會喚醒在等待線程資源
25   
26      notify();
27   
28  }
 

配置

在Server.xml文件中配置Connector屬性

maxThreads

最大線程數

minSpareThreads

最小空閒線程數

maxSpareThreads

最大空閒線程數

例如:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" maxThreads="800" minSpareThreads="50" maxSpareThreads="500" />

 

TP-Monitor線程

 

功能

監控ThreadPool線程池的空閒線程,回收比最大空閒線程數多出的空閒線程。

線程類:ThreadPool.MonitorRunnable

/**
 
 * 定期清理空閒線程
 
*/
 
public static class MonitorRunnable implements Runnable {
 
    ThreadPool p;
10   
11      Thread     t;
12   
13      int interval =WORK_WAIT_TIMEOUT;
14   
15      boolean shouldTerminate ;
16   
17      MonitorRunnable(ThreadPool p) {
18   
19          this.p =p;
20   
21          this.start();
22   
23      }
24   
25      public void start() {
26   
27          shouldTerminate = false ;
28   
29          t = new Thread(this);
30   
31          t.setDaemon( p.getDaemon() );
32   
33          t.setName( p.getName() + "-Monitor");
34   
35          t.start();
36   
37       }
38   
39      public void setInterval(int i ) {
40   
41          this.interval =i;
42   
43      }
44   
45      public void run() {
46   
47          while(true ) {
48   
49              try {
50   
51                  //Wait一段時間
52   
53                  synchronized(this ) {
54   
55                     this.wait(interval );
56   
57                  }
58   
59                  // Check if should terminate.
60   
61                  // termination happens when the pool is shutting down.
62   
63                  if(shouldTerminate ) {
64   
65                     break;
66   
67                  }
68   
69                  //回收空閒線程
70   
71                  p.checkSpareControllers();
72   
73             } catch(Throwable t) {
74   
75                  ThreadPool. log.error("Unexpected exception" , t);
76   
77             }
78   
79         }
80   
81      }
82   
83      public void stop() {
84   
85          this.terminate();
86   
87      }
88   
89      /** 停止monitor線程
90   
91       */
92   
93      public synchronized void terminate() {
94   
95          shouldTerminate = true ;
96   
97          this.notify();
98   
99      }
100   
101  }

ThreadPool.checkSpareControllers方法,用來被TP-Monitor線程調用回收工作線程。

/**
 
 * 被TP-Monitor線程用來回收線程
 
 */
 
protected synchronized void checkSpareControllers() {
 
    if(stopThePool ) {
10   
11          return;
12   
13      }
14   
15      //如果當前空閒線程數大於最大空閒線程數
16   
17      if((currentThreadCount - currentThreadsBusy) > maxSpareThreads) {
18   
19          //回收比最大空閒線程數多出的空閒線程
20   
21          int toFree = currentThreadCount -
22   
23          currentThreadsBusy -
24   
25          maxSpareThreads;
26   
27          for(int i = 0 ; i < toFree ; i++) {
28   
29              ControlRunnable c = pool[currentThreadCount - currentThreadsBusy - 1];
30   
31              c.terminate();
32   
33              pool[currentThreadCount - currentThreadsBusy - 1] = null;
34   
35              currentThreadCount --;
36   
37          }
38   
39      }
40   
41  }
    

所屬線程池

ThreadPool線程池

 

ContainerBackgroundProcessor線程

 

功能

容器後臺線程,只有設置backgroundProcessorDelay大於0的容器纔會啓動ContainerBackgroundProcessor線程。該線程會調用當前容器的backgroundProcess方法,並且遞歸調用 backgroundProcessorDelay值小於等於0的子容器的方法。

從源碼中看到只有StandardEngine設置了這個backgroundProcessorDelay值爲10,所以只有StandardEngine容器啓動ContainerBackgroundProcessor線程, 而其它StandardHost, StandardContext設置的值都是-1。

/**
 
 * 創建一個新的StandardEngine組件,並綁定默認的基礎Valve。
 
 */
 
public StandardEngine() {
 
    super();
10   
11      pipeline.setBasic(new StandardEngineValve());
12   
13      /* Set the jmvRoute using the system property jvmRoute */
14   
15      try {
16   
17          setJvmRoute(System. getProperty("jvmRoute"));
18   
19      } catch(Exception ex) {
20   
21      }
22   
23      // Engine將擁有reloading線程
24   
25      backgroundProcessorDelay = 10;
26   
27  }
 

線程類:ContainerBase.ContainerBackgroundProcessor

 
 
/*  
  
 * ContainerBase的保護線程類,調用當前容器的backgroundProcess方法,並在一個固定延時後,  
  
 * 用它的子容器的backgroundProcess方法  
  
 */  
  
protected class ContainerBackgroundProcessor implements Runnable {  
10      
11      public void run() {  
12      
13          while (!threadDone ) {  
14      
15              try {  
16      
17                  Thread. sleep(backgroundProcessorDelay * 1000L);  
18      
19              } catch (InterruptedException e) {  
20      
21                  ;  
22      
23              }  
24      
25              if (!threadDone ) {  
26      
27                  //獲得當前容器,作爲父容器  
28      
29                  Container parent = (Container) getMappingObject();  
30      
31                  ClassLoader cl =  
32      
33                  Thread. currentThread().getContextClassLoader();  
34      
35                  if (parent.getLoader() != null) {  
36      
37                      cl = parent.getLoader().getClassLoader();  
38      
39                  }  
40      
41                  //處理父容器和所有的子容器  
42      
43                  processChildren(parent, cl);  
44      
45             }  
46      
47          }  
48      
49      }  
50       
51      //處理父容器和所有的子容器 
52     
53      protected void processChildren(Container container, ClassLoader cl) { 
54     
55          try { 
56     
57              //如果父容器的loader不爲null,則將當前線程的上下文類加載器contextClassLoader設置爲父容器 
58     
59              //的loader的類加載器 
60     
61              if (container.getLoader() != null) { 
62     
63                  Thread. currentThread().setContextClassLoader 
64     
65                          (container.getLoader().getClassLoader()); 
66     
67               } 
68     
69              //調用父容器的backgroundProcess方法 
70     
71              container.backgroundProcess(); 
72     
73         } catch (Throwable t) { 
74     
75             log.error("Exception invoking periodic operation: " , t); 
76     
77         } finally { 
78     
79             Thread. currentThread().setContextClassLoader(cl); 
80     
81         } 
82     
83         //獲得父容器的所有子容器 
84     
85         Container[] children = container.findChildren(); 
86     
87         for (int i = 0; i < children.length; i++) { 
88     
89             //如果子容器的backgroundProcessorDelay小於等於0,則遞歸處理子容器 
90     
91             if (children[i].getBackgroundProcessorDelay() <= 0) { 
92     
93                 processChildren(children[i], cl); 
94     
95             } 
96     
97         } 
98     
99     } 
100        
101  }

所屬線程池

發佈了28 篇原創文章 · 獲贊 16 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章