Tomcat的主要功能简单概括起来就是接收请求,处理请求,返回结果,而接收请求和返回结果的过程都需要Connector组件参与。那么我们以Http/1.1对应的连接器为例,看下Connector组件启动过程的源码。Connector组件启动的主要逻辑在JIoEndpoint类的start方法中。
public void start()
throws Exception {
// Initialize socket if not done before
if (!initialized) {
init();
}
if (!running) {
running = true;
paused = false;
// Create worker collection
if (executor == null) {
workers = new WorkerStack(maxThreads);
}
// Start acceptor threads
for (int i = 0; i < acceptorThreadCount; i++) {
Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
acceptorThread.setPriority(threadPriority);
acceptorThread.setDaemon(daemon);
acceptorThread.start();
}
}
}
方法中首先实例化一个WorkerStack对象,该对象负责管理work线程,类中有个work数组负责存放work线程,在实例化WorkerStack对象的时候会将这个数组的大小初始化为200,WorkerStack对象利用这个数组实现了一个栈的数据结构。work线程的作用是负责处理请求,因此我们可以得出这样一个结论:Tomcat中处理请求的线程数量默认的最大是200。work线程中除了run方法外还有两个比较重要的方法,分别是assign和await。
synchronized void assign(Socket socket) {
// Wait for the Processor to get the previous Socket
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
}
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
return (socket);
关于这两个方法的作用我们待会儿再讲,接着看start方法。方法中通过一个for循环启动Acceptor线程,顾名思义Acceptor线程的作用就是接收请求。Tomcat启动了多少个线程来接收请求呢?答案是一个,前面我们有说到,在执行JIoEndpoint类的init方法的时候将acceptorThreadCount的值设置为1,表示启动接收线程的数量。那么问题来了,Tomcat只启动了一个接收线程性能会不会有问题,能快速的响应多个请求吗?其实不然,接收线程设置成一个是没有问题的,因为它只负责接收,收到请求后就扔给work线程去处理,然后Acceptor继续接收请求。相比于接收请求,处理过程将更会耗时,这就是为什么work线程有多个,而Acceptor线程只有一个的原因了。
Acceptor线程接收请求并将请求交给work线程处理的逻辑都在run方法中。首先通过Socket socket = serverSocketFactory.acceptSocket(serverSocket)
这样一句代码使socket处于监听状态,监听端口默认是8080,此时程序处于阻塞状态,直到有请求到来。当接收到请求后调用processSocket方法先创建并启动work线程,然后调用work线程的assign方法。`work线程启动过程中,会执行run方法里的代码:
public void run() {
// Process requests until we receive a shutdown signal
while (running) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null)
continue;
// Process the request from this socket
if (!setSocketOptions(socket) || !handler.process(socket)) {
// Close socket
try {
socket.close();
} catch (IOException e) {
}
}
// Finish up this request
socket = null;
recycleWorkerThread(this);
}
}
方法中首先执行await方法,由于此时available的值为false,因此work线程会处于等待状态。此时会调用work线程的assign方法,因为available的值为false,因此assign方法不会处于进入wait方法,并且将带有请求信息的socket对象传递给this.socket,设置available的值,最重要的一点是唤醒所有等待的线程。因此assign和await方法的执行顺序是先执行await阻塞程序等待socket的到来,然后执行assign方法传递socket对象,唤醒work线程。assign执行完之后,因为await方法而处于等待状态的work线程被唤醒了,并且获取到了从Acceptor线程那边传递过来的socket对象,而socket对象中又有请求信息,也就是说Acceptor线程将处理请求的任务给了work线程,而Acceptor线程也可以接受新的请求了,就这样把接受请求和处理请求的线程分开了。