开发者学堂课程【高校精品课-厦门大学 -JavaEE 平台技术:Tomcat 的并发原理】学习笔记,与课程紧密联系,让用户快速学习知识
课程地址:https://developer.aliyun.com/learning/course/80/detail/15913
Tomcat 的并发原理
内容介绍:
一、 Tomcat 的并发原理
二、 Tomcat 的运行模式
三、 Tomcat 的并发原理的特点
一、Tomcat 的并发原理
Tomcat 的并发是依赖于多线程来实现的,每一个线程都需要消耗一定的内存,用于存储它的运行栈。当线程的数量变多时,CPU 也需要消耗更多的资源进行线程的切换。虽然多线程能带来很好的并发性,但是需要根据每一台服务器的物理资源的情况去限制这台服务器上可以运行的线程数。所以在 Tomcat 中会用一个线程池的概念来限制在这台服务器上可以跑的线程的数目。在 Tomcat 中每一个线程都需要通过 io 操作从客户端读取 HTTP 的请求里的内容并把运行的结果作为 HTTP Response 通过网络传回给客户端。这种网络 io 的操作会给服务器的性能带
来很大的影响。
二、Tomcat 的运行模式
1.简单阐述
Tomcat 一共有三种运行模式,分别为 BIO ——阻塞 IO , NIO——非阻塞 IO , APR —— Apache Portable Runtime 。这三种运行模式主要的差别在于它的网络 io 的
处理模式是不一样的。
(1)BIO :它是最简单的,是 Tomcat7 以下的默认运行模式。它的运行模式是依赖于 java.io 包里的那些 API 来进行网络 io 操作的。传统的 Java io的操作是阻塞的,也就是通过一个流去读或者写数据的时候,它是会等待这个的读写完成才能继续往后运行。在这样一种模式下,线程里做网络 io 是会阻塞会等待,而线程数又是有限的,这样会使得整个服务器的物理资源的利用率不高。为了提升网络 io 的效
率,Tomcat 提供了 NIO 和 APR 两种运行模式。
(2) NIO :是基于 JDK1.4 以后所提供的异步 io的API 来实现的网络 io 。这种 NIO 的实现方式在 Tomcat 线程中间读写网络的数据是不会被阻塞的,会使得 Tomcat 的线程的利用率大幅度的提升。
(3) APR :是在操作系统的级别来解决异步 io 的问题。它与 NIO 相比可以更大幅度地提升服务器的 io 的处理和响应的性能。由于不同的操作系统是不一样的, APR 利用 Apache 的可执行的运行库,在不同的操作系统上实现一种高并发。但是 APR 在使用的时候也相对比较麻烦一点,因为它需要安装 Apache 的第三方依赖包。
2.重点讨论
这节课程重点讨论的是基于 Java 虚拟机的 BIO 和 NIO 这两种运行模式。
(1)BIO 的运行模式:当 Tomcat 启动的时候,会创建并启动一个线程叫做 Acceptor ,这个线程负责接收客户端的请求。当 Acceptor 接到了一个客户端的请求以后,它会创建一个 Socket ,然后把 Socket 置于 Socket Processor 对象中间,然后从线程池里选择一个空闲的线程来执行 Socket Processor 对象。在
Socket Processor 对象中间,它会读取Socket 来执行网络 io 。
但是这个过程是会被阻塞的,在读到 HTTP 的请求里的值以后,它创建了一个 HTTP Request 对象,然后执行 Servlet 对象的 service 的方法,这是一个简单的 HTTP 的请求的处理过程。假设当这个请求正在处理过程中间,Servlet 容器又收到了另一个请求, Acceptor 的线程则会创建一个新的 Socket 放到一个新的 Socket Processor 对象中间,从线程池里选择另外一个空闲的线程来服务这个请求,并读取网络 io 去执行 Servlet 对象的 service 方法。 Tomcat 的容器并不关心请求是针对同一个 Servlet 还是不同的 Servlet ,它都会放到不同的线程中间去执行。如果容器收到同一个 Servlet 对象的多个请求的时候,这个 Servlet 对象的 service 方法会在多个线程中间并发执行。
当 service 方法调用完成以后,Servlet 容器会把这个线程放回到线程池里。如果当一个请求来到的时候,线程池里的线程没有空闲都在为其它的请求提供服务,这个请求则会被阻塞放在排队队列中间等待,直到线程池里有线程空闲出来。
Servlet 容器可以配置它的排队的最大排队数,如果超过了最大排队数, Servlet 容器则会拒绝相应的 HTTP请求。
(1)NIO 的运行模式:它的运行模式和 BIO 非常的像,它的差别就在于它的网络 io 的处理方式是不一样的。 NIO 的运行模式同样由 Acceptor 线程去负责接受请求。
当 Servlet 容器收到客户端的请求以后 Acceptor 会建立 Servlet ,但是它不是直接把 Socket 放到 Socket Processor 对象中间,而是创建一个 NioChannel ,把 Socket 以及创建出来的 Buffer 置于 NioChannel 中, NioChannel 会从 Socket 中间读取数据放到 Buffer 中间;当数据读取完成以后,NioChannel 会发出事件去通知 Servlet 容器的线程去读取这个数据。Acceptor 会把 NioChannel 登记到一个事件列表里,然后由一个 Poller 的线程去负责轮询这个事件列表,去看哪一个 Nio Channel 已经接受完成了数据。Servlet 容器的线程就由此得知数据的读取完成。
如果某一个 NioChannel 已经完成了数据的接收,则创建一个 Socket Processor 对象,然后把这个 Socket 放到Socket Processor 里,后面的过程跟 BIO 的运行模式是一样的。不同的是,在线程中如果去读取数据的话,这个数据其实已经存在于 Buffer 中,所以它的读取速度是会比较快而不会因为网络的延迟被阻塞到。这样就
能提升线程的运行速度,它不会因为网络 io 而线程去阻塞。
与 BIO 相比,如果网络的性能是整个系统的瓶颈的话;NIO 这种模式可以大幅度的
提高系统的性能,它可以支持系统更多的请求。
三、 Tomcat 并发处理的特点
Tomcat 的并发处理是依赖于多线程来完成的,它主要的思想是无论有多少个请
求,对于内存的消耗或者 CPU 的消耗要相对是比较恒定的。
1. 单实例 Servlet 对象
就是无论同时有多少请求过来,在 Servlet 容器中间,每一个 Servlet 类只有一个
实例对象,它不会随着并发请求的增多而增多的。
2. 基于线程池的多线程
通过线程池来固定了多线程的数目。当线程同时请求超过了这个线程池的数目的时
候,它就会处于一个等待队列中间,这样也固定了线程对于内存开销的一个消耗。
3. Servlet 对象线程不安全
这是它的第一个特点所造成的线程不安全。因为针对同一个 Servlet 的多个请求,其实是共用了同一个 Servlet 的实例对象,意味着这个 Servlet 实例对象的属性会被多个线程所共享。因此,在大多数情况下,编写程序时不会在 Servlet 的对象上去写任何的属性。如果要写属性是需要去做加锁,而加锁又会限制线程的并发性,
所以往往采取了不在 Servlet 对象上去写属性。