赞
踩
我们知道Tomcat既要接收客户端请求,同时需要处理业务逻辑,做相关的转发。
那么Server端无疑需要根据其能力,划分为2个部分。
此处用最常用的Nio同步非阻塞模型来看
数据来到网卡后,只有内核空间可以执行对硬件的操作(将数据读取到),并回调注册了回调方法的阻塞等待任务,此时task_struct任务再次被激活,并来到运行任务队列,当CPU执行到该任务时,将线程上下文信息恢复到寄存器。CPU从内核态切换到用户态,将内核空间的数据拷贝至用户态堆数据中。(当然由于mmap映射区的存在,一些场景下可以实现不拷贝数据,而知识获取内核空间的地址,从然实现获取、修改内容的能力,进而实现零拷贝)
<Service name="Catalina"> <!-- namePrefix: 线程前缀 maxThreads: 最大线程数,默认设置 200,一般建议在 500 ~ 800,根据硬件设施和业务来判断 minSpareThreads: 核心线程数,默认设置 25 prestartminSpareThreads: 在 Tomcat 初始化的时候就初始化核心线程 maxQueueSize: 最大的等待队列数,超过则拒绝请求 ,默认 Integer.MAX_VALUE maxIdleTime: 线程空闲时间,超过该时间,线程会被销毁,单位毫秒 className: 线程实现类,默认org.apache.catalina.core.StandardThreadExecutor --> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-%d" prestartminSpareThreads="true" maxThreads="500" minSpareThreads="8" maxIdleTime="10000"/> <!-- maxThreads: 内置线程池最大线程数,默认设置 200 minSpareThreads: 内置线程池核心线程数,默认设置 10 maxConnections:与tomcat建立的最大socket连接数,默认8192 acceptCount: 操作系统底层请求队列的最大长度 ,默认值为100 --> <Connector port="8080" protocol="HTTP/1.1" executor="tomcatThreadPool" connectionTimeout="20000" maxConnections="8192" maxThreads="500" redirectPort="8443" URIEncoding="UTF-8"/> <Engine xxx/> ...... </Service>
/**
- Shared latch that allows the latch to be acquired a limited number of times
- after which all subsequent requests to acquire the latch will be placed in a
- FIFO queue until one of the shares is returned.
*/
上文说过Tomcat因为最大线程数的限制,必须针对request请求进行限流,那么限流的方式如何?
LimitLatch需要实现何种功能?
1、计数器
2、计数自增,当达到预设限制时,需要拒绝
我们对比Tomcat限流器LimitLatch和Doug Lea的CountDownLatch类结构比较。我们知道CountDownLatch的作用是闭锁AQS实现及各种锁详细
CountDownLatch,在计数count量减1,直至所有均完成时结束。而次数需要做限流,则需要count自增,直至count来到Limit。且看Tomcat的核心修改如下。
@Override
protected int tryAcquireShared(int ignored) {
long newCount = count.incrementAndGet();
if (!released && newCount > limit) {
// Limit exceeded
count.decrementAndGet();
return -1;
} else {
return 1;
}
}
JDK常规的线程池需要先把Task交给核心线程,当核心线程数未满时则创建核心线程。核心线程池满了则将Task交给Queue,如何阻塞队列也满了,此时才创建非核心线程。下面是此前整理的线程池详解中总结的线程池流程图。
看JDK线程池执行任务源码:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); //Step1 如果运行的核心线程数<定义核心线程数,则创建核心线程 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } //Step2 判断线程池是否仍在运行,尝试加入到队列 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } //Step3 Queue.offer添加任务队列失败时才会添加非核心线程执行command任务 else if (!addWorker(command, false)) reject(command); }
public void createExecutor() {
internalExecutor = true;
//注意看队列队列使用的是Tomcat自定义实现的LinkedBlockingQueue
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
maxThreads: 最大线程数,默认设置 200,一般建议在 500 ~ 800,根据硬件设施和业务来判断
minSpareThreads: 核心线程数,默认设置 25
@Override
public boolean offer(Runnable o) {
//we can't do any checks
if (parent==null) return super.offer(o);
//当前线程数已达到最大线程数,新的任务添加到任务队列
if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
//当前有空闲线程,只需将其添加到任务队列
if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o);
//当前非核心线程数未满,返回false,强制创建线程
if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
//if we reached here, we need to add it to the queue
return super.offer(o);
}
是的,需要再次摩拜Doug Lea,大神的代码处处有惊喜!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。