1、Tomcat如何扩展Java线程池?
通过比较FixedThreadPool和CachedThreadPool,我们发现它们传给ThreadPoolExecutor的参数有两个关键点:
- 是否限制线程个数。
- 是否限制队列长度。
对于Tomcat来说,这两个资源都需要限制,也就是说要对高并发进行控制,否则CPU和内存有资源耗尽的风险。
Tomcat扩展了Java线程池的核心类ThreadPoolExecutor,并重写了它的execute方法,定制了自己的任务处理流程。同时Tomcat还实现了定制版的任务队列,重写了offer方法,使得在任务队列长度无限制的情况下,线程池仍然有机会创建新的线程。
2、Tomcat如何支持WebSocket?
WebSocket技术实现了Tomcat与浏览器的双向通信,Tomcat可以主动向浏览器推送数据,可以用来实现对数据实时性要求比较高的应用。这需要浏览器和Web服务器同时支持WebSocket标准,Tomcat启动时通过SCI技术来扫描和加载WebSocket的处理类ServerEndpoint,并且建立起了URL到ServerEndpoint的映射关系。
当第一个WebSocket请求到达时,Tomcat将HTTP协议升级成WebSocket协议,并将该Socket连接的Processor替换成UpgradeProcessor。这个Socket不会立即关闭,对接下来的请求,Tomcat通过UpgradeProcessor直接调用相应的ServerEndpoint来处理。
3、Tomcat的对象池技术
Tomcat和Jetty中的对象池技术
public class SynchronizedStack<T> {
//内部维护⼀个对象数组,⽤数组实现栈的功能
private Object[] stack;
//这个⽅法⽤来归还对象,⽤synchronized进⾏线程同步
public synchronized boolean push(T obj) {
++this.index;
if (this.index == this.size) {
if (this.limit != -1 && this.size >= this.limit) {
--this.index;
return false;
}
this.expand();//对象不够⽤了,扩展对象数组
}
this.stack[this.index] = obj;
return true;
}
//这个⽅法⽤来获取对象
public synchronized T pop() {
if (this.index == -1) {
return null;
} else {
T result = this.stack[this.index];
this.stack[this.index--] = null;
return result;
}
}
//扩展对象数组⻓度,以2倍⼤⼩扩展
private void expand() {
int newSize = this.size * 2;
if (this.limit != -1 && newSize > this.limit) {
newSize = this.limit;
}
//扩展策略是创建⼀个数组⻓度为原来两倍的新数组
Object[] newStack = new Object[newSize];
//将⽼数组对象引⽤复制到新数组
System.arraycopy(this.stack, 0, newStack, 0, this.size);
//将stack指向新数组,⽼数组可以被GC掉了
this.stack = newStack;
this.size = newSize;
}
}
这个代码逻辑比较清晰,主要是SynchronizedStack内部维护了一个对象数组,并且用数组来实现栈的接口:push和pop方法,这两个方法分别用来归还对象和获取对象。你可能好奇为什么Tomcat使用一个看起来比较简单的SynchronizedStack来做对象容器,为什么不使用高级一点的并发容器比如
ConcurrentLinkedQueue呢?
这是因为SynchronizedStack用数组而不是链表来维护对象,可以减少结点维护的内存开销,并且它本身只支持扩容不支持缩容,也就是说数组对象在使用过程中不会被重新赋值,也就不会被GC。这样设计的目的是用最低的内存和GC的代价来实现无界容器,同时Tomcat的最大同时请求数是有限制的,因此不需要担心对象的数量会无限膨胀。
4、Tomcat高性能、高并发之道
Tomcat中用到了大量的高性能、高并发的设计,我总结了几点:I/O和线程模型、减少系统调用、池化、零拷贝、高效的并发编程。
1、I/O和线程模型
采用非阻塞I/O或者异步I/O
2、减少系统调用
系统调用最多的就是网络通信操作了,一个Channel上的write就是系统调用,为了降低系统调用的次数,最直接的方法就是使用缓冲,当输出数据达到一定的大小才flush缓冲区
3、池化、零拷贝
池化的本质就是用内存换CPU;而零拷贝就是不做无用功,减少资源浪费。
4、高效的并发编程
1、缩小锁的范围
2、用原子变量和CAS取代锁
3、并发容器的使用
4、volatile关键字的使用
网友评论