-
每天都在用,但你知道 Tomcat 的线程池有多努力吗?
2020-04-26 21:59:58说点不一样的线程池执行策略和线程拒绝策略,探讨怎么让线程池先用完最大线程池再把任务放到队列中。 荒腔走板 大家好,我是 why,一个四川程序猿,成都好男人。 先是本号的特色,技术分享之前先简短的荒腔走板聊聊...这是why的第 45 篇原创文章。说点不一样的线程池执行策略和线程拒绝策略,探讨怎么让线程池先用完最大线程池再把任务放到队列中。
荒腔走板
大家好,我是 why,一个四川程序猿,成都好男人。
先是本号的特色,技术分享之前先简短的荒腔走板聊聊生活。让文章的温度更多一点点。
上面的图是我在一次跑步的过程中拍的。活动之前赛事方搞了个留言活动,收集每公里路牌的一个宣传语。
我的留言有幸被选中了:
每人知道你在坚持什么,但你自己心里应该清楚。
是在说跑马拉松,也是在说其他的事情。
我记得那天的太阳,骄阳似火,路上的树荫也非常的少。苦就苦在我还报的是超级马拉松(说是超级马拉松,其实就是一个全马 42 km加最后 3 km纯上坡的马拉松)
到底有多晒,我给你看一下对比:
酷暑难耐,以至于 30 公里左右的地方我的心里出现了两个小人:
一个说:我好累啊,我跑不动了,我要退赛。
一个说:好呀好呀,我也好晒啊,退赛退赛。
我说:呸,看你们两个不争气的东西,让我带你去终点
于是在 36 公里的地方碰到了我提交的标语,非常开心,停下来拍了几张照片。给自己说:坚持不住的时候再坚持一下。
最后的 3 公里上坡,抽筋了不知道多少次。远远看见终点拱门的时候我突然想到了在敦煌的时候悟出的一句话:自己给自己的辛苦,不是辛苦,是幸福。
好了,说回文章。
违背直觉的JDK线程池
先用 JDK 线程池来开个题。
还是用我之前这个文章《如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答。》“先劝退一波”这一小节里面的例题:
问:这是一个自定义线程池,假设这个时候来了 100 个比较耗时的任务,请问有多少个线程在运行?
正确回答在之前的文章中回答了,这里不在赘述。
但是我面试的时候曾经遇到过很多对于 JDK 线程池不了解的朋友。
而这些人当中大多数都有一个通病,那就是遇到不太会的问题,那就去猜。
面试者遇到这个不会的题的时候,表面上微微一笑,实际上我都已经揣摩出他们的内心活动了:
MD,这题我没背过呀,但是刚刚听面试官说核心线程数是 10,最大线程数是 30。看题也知道答案不是 10 就是 30。
选择题,百分之 50 的命中率,难道不赌一把?
等等,30 是最大线程数?最大?我感觉就是它了。
于是在电光火石一瞬间的思考后,和我对视起来,自信的说:
于是我也是微微一笑,告诉他:下去再了解一下吧,我们聊聊别的。
确实,如果完全不了解 JDK 线程池运行规则,按照直觉来说,我也会觉得应该是,不管是核心还是最大线程数,有任务来了应该先把线程池里面可用的线程用完了,然后再把任务提交到队列里面去排队。
可惜 JDK 的线程池,就是反直觉的。
那有符合我们直觉的线程池吗?
有的,你经常用的的 Tomcat ,它里面的线程池的运行过程就是先把最大线程数用完,然后再提交任务到队列里面去的。
我带你剖析一下。
Tomcat线程池
先打开 Tomcat 的 server.xml 看一下:
眼熟吧?哪一个学过 java web 的人没有配置过这个文件?哪一个配置过这个文件的人没有留意过 Executor 配置?
具体的可配置项可以查看官方文档:
http://tomcat.apache.org/tomcat-9.0-doc/config/executor.html
同时我找到一个可配置的参数的中文说明如下:
注意其中的第一个参数是 className,图片中少了个字母 c。
然后还有两个参数没有介绍,我补充一下:
1.prestartminSpareThreads:boolean 类型,当服务器启动时,是否要创建出最小空闲线程(核心线程)数量的线程,默认值为 false 。
2.threadRenewalDelay:long 类型,当我们配置了 ThreadLocalLeakPreventionListener 的时候,它会监听一个请求是否停止。当线程停止后,如果有需要,会进行重建,为了避免多个线程,该设置可以检测是否有 2 个线程同时被创建,如果是,则会按照该参数,延迟指定时间创建。 如果拒绝,则线程不会被重建。默认为 1000 ms,设定为负值表示不更新。
我们主要关注 className 参数,如果不配置,默认实现是:
org.apache.catalina.core.StandardThreadExecutor
我们先解读一下这个方法(注意,本文中 Tomcat 源码版本号为:10.0.0-M4):
org.apache.catalina.core.StandardThreadExecutor#startInternal
从 123 行到 130 行,就是构建 Tomcat 线程池的地方,很关键,我解读一下:
123行
taskqueue = new TaskQueue(maxQueueSize);
创建一个 TaskQueue 队列,这个队列是继承自 LinkedBlockingQueue 的:
该队列上的注释值得关注一下:
主要是说这是一个专门为线程池设计的一个任务队列。配合线程池使用的时候和普通队列有不一样的地方。
同时传递了一个队列长度,默认为 Integer.MAX_VALUE:
124行
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
构建一个 ThreadFactory,其三个入参分为如下:
namePrefix:名称前缀。可以指定,其默认是“tomcat-exec-”。
daemon:是否以守护线程模式启动。默认是 true。
priority:线程优先级。是一个 1 到 10 之前的数,默认是 5。
125行
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
构建线程池,其 6 个入参分别如下:
这个具体含义我就不解释了,和 JDK 线程池是一样的。
只是给大家看一下默认参数。
另外还需要十分注意的一点是,这里的 ThreadPoolExecuteor 是 Tomcat 的,不是 JDK 的,虽然名字一样。
看一下 Tomcat 的 ThreadPoolExecuteor注释,里面提到了两个点,一是已提交总数,二是拒绝策略。后面都会讲到。
126行
executor.setThreadRenewalDelay(threadRenewalDelay);
设置 threadRenewalDelay 参数。不是本文重点,可以先不关心。
127 - 129行
if (prestartminSpareThreads) { executor.prestartAllCoreThreads(); }
设置是否预启动所有的核心线程池,这个参数在之前文章中也有讲到过。
prestartminSpareThreads 参数默认是 false。但是我觉得这个地方你设置为 true 也是多次一举。完全没有必要。
为什么呢?
因为在 125 行构建线程池的时候已经调用过这个方法了:
从源码可以看出,不管你调用哪一个线程池构造方法,都会去调用 prestartAllCoreThreads 方法。
所以,这算不算 Tomcat 的一个小 Bug 呢?快拿起你的键盘给它提 pr 吧。
130行
taskqueue.setParent(executor);
这行代码非常关键。没有这行代码,Tomcat 的线程池则会表现的和 JDK 的线程池一样。
拿下面的程序举例:
自定义线程池最多可以容纳 150+300 个任务。
当 24 行注释的时候,Tomcat 线程池运行的过程和 JDK 线程池的运行过程一样,运行的线程数只会是核心程序数 5。
当 24 行取消注释的时候,Tomcat 线程池就会一直创建线程个数到 150 个,然后把剩下的任务提交到自定义的 TaskQueue 队列里面去。
我再提供一个复制粘贴直接运行版本,你分别运行一下,试一试,看看结果:
public class TomcatThreadPoolExecutorTest {
public static void main(String[] args) throws InterruptedException { String namePrefix = "why不止技术-exec-"; boolean daemon = true; TaskQueue taskqueue = new TaskQueue(300); TaskThreadFactory tf = new TaskThreadFactory(namePrefix, daemon, Thread.NORM_PRIORITY); ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 150, 60000, TimeUnit.MILLISECONDS, taskqueue, tf); //taskqueue.setParent(executor); for (int i = 0; i < 300; i++) { try { executor.execute(() -> { logStatus(executor, "创建任务"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }); } catch (Exception e) { e.printStackTrace(); } } Thread.currentThread().join(); } private static void logStatus(ThreadPoolExecutor executor, String name) { TaskQueue queue = (TaskQueue) executor.getQueue(); System.out.println(Thread.currentThread().getName() + "-" + name + "-:" + "核心线程数:" + executor.getCorePoolSize() + "\t活动线程数:" + executor.getActiveCount() + "\t最大线程数:" + executor.getMaximumPoolSize() + "\t总任务数:" + executor.getTaskCount() + "\t当前排队线程数:" + queue.size() + "\t队列剩余大小:" + queue.remainingCapacity()); }
}
接着就去分析这行代码的用途,看看这一行代码,怎么就反转了 JDK 线程池的运行过程。
源码之下无秘密
如果你对 JDK 线程池的源码熟悉一点的话,你大概能猜到 Tomcat 肯定是在控制新建线程的地方做了手脚,也就是下面这个地方:
PS:需要说明一下的是,上面的截图是 JDK 线程池的 execute 方法。因为 Tomcat 线程池的提交也是复用的这个方法。但是 workQueue 不是同一个队列。
那你先把工作流程和各个参数都摸熟了,然后写个 Demo ,接着去疯狂的 Debug 吧。然后你总会找到这个地方的,而且你会发现,不难找。
好了,上面主要关注我圈起来的部分。
在截图的 1371 行,如果没有把任务成功放到队列里面(前提是线程池是运行状态),则会执行 1378 行的逻辑,而这个逻辑,就是创建非核心线程的逻辑。
所以,经过上面的推导之后,一切都清晰了,Tomcat 只需要在自定义队列的 offer 方法中做文章即可。
所以,我们重点关注一下该方法:
org.apache.tomcat.util.threads.TaskQueue#offer
为了更加直观的看出来其运行流畅,我在第 80 行打了个断点运行程序如下:
可以看到里面的几个参数,下面的讲解会用到这里面的参数:
第一个 if 判断
首先第一个 if,判断 parent 是否为空:
从断点运行参数截图可以看出,这里的 parent 就是 Tomcat 的 ThreadPoolExecutor 类。
当 parent 为 null 时,直接调用原始的 offer 方法。
所以,还记得我前面说的吗?
现在你知道为什么了吧?
源码,就是这个源码。道理,就是这么个道理。
所以,这里不为空,不满足条件,进入下一个 if 判断。
第二个 if 判断
首先,需要明确的是,能进入到第二个判断的时候,当前运行中的线程数肯定是大于等于核心线程数(因为已经在执行往队列里面放的逻辑了,说明核心线程数肯定是满了),小于最大线程数的。
其中 getPoolSize 方法是获取线程池中当前运行的线程数量:
所以,第二个 if 判断的是运行中的线程数是否等于最大线程数。如果等于,说明所有线程都在工作了,把任务扔到队列里面去。
从断点运行参数截图可以看到, 当前运行数为 5 ,最大线程数为 150。不满足条件,进入下一个 if 判断。
第三个 if 判断
首先我们看看 getSubmittedCount 获取的是个什么玩意:
getSubmittedCount 获取的是当前已经提交但是还未完成的任务的数量,其值是队列中的数量加上正在运行的任务的数量。
从断点运行参数截图可以看到,当前情况下该数据为 6。
而 parent.getPoolSize() 为 5。
不满足条件,进入下一个 if 判断。
但是这个地方需要多说一句的是,如果当已经提交但是还未完成的任务的数量小于线程池中运行线程的数量时,Tomcat 的做法是把任务放到队列里面去,而不是立即执行。
其实这样想来也是很符合逻辑且简单的做法的。
反正有空闲的线程嘛,扔到队列里面去就被空闲的线程消费了。又何必立即执行呢?破坏流程不说,还需要额外实现。
出力不讨好。没必要。
第四个 if 判断
这个判断就很关键了。
如果当前运行中的线程数量小于最大线程数,返回 false。
注意哦,前面的几个 if 判断都是不满足条件就放入队列哦。而这里是不满足条件,就返回 false。
返回 false 意味着什么?
意味着要执行 1378 行代码,去创建线程了呀。
所以,整个流程图大概就是这样:
再聊聊拒绝策略
拒绝策略需要看这个方法:
org.apache.tomcat.util.threads.ThreadPoolExecutor#execute(java.lang.Runnable, long, java.util.concurrent.TimeUnit)
看一下该方法上的注释:
如果队列满了,则会等待指定时间后再次放入队列。
如果再次放入队列的时候还是满的,则抛出拒绝异常。
这个逻辑就类似于你去上厕所,发现坑位全都被人占着。这个时候你的身体告诉你,你括弧肌最多还能在忍一分钟。
于是,你掐着表在门口,深呼吸,闭眼冥想,等了一分钟。
运气好的,再去一看:哎,有个空的坑位了,赶紧占着。
运气不好,再去一看:哎,还是没有位置,怎么办呢?抛异常吧。具体怎么抛就不说了,自行想象。
所以我们看看这个地方,Tomcat 的代码是怎么实现的:
catch 部分首先判断队列是不是 Tomcat 的自定义队列。如果是,则进入这个 if 分支。
关键的逻辑就在这个 if 判断里面了。
可以看到 172 行:
if (!queue.force(command, timeout, unit))
调用了队列的 force 方法。我们知道 BlockingQueue 是没有 force 方法的。
所以这个force 是 Tomcat 自定义队列特有的方法:
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException { if (parent == null || parent.isShutdown()) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning")); //forces the item onto the queue, to be used if the task is rejected return super.offer(o,timeout,unit); }
进去一看发现:害,也不过如此嘛。就是对原生 offer 方法的一层包装而已。
如果成功加入队列则万事大吉,啥事没有。
如果没有成功加入队列,则抛出异常,并维护 submittedCount 参数。
前面说过:submittedCount 参数 = 队列中的任务个数 + 正在运行的任务数。
所以,这里需要进行减一操作。
拒绝策略就说完了。
但是这个地方的源码是我带你找到的。如果你想自己找到应该怎么操作呢?
你想啊,你是想测试拒绝策略。那只要触发其拒绝策略就行了。
比如下面这样:
给一个只能容纳 450 个任务的线程池提交 500 个任务。
然后就会抛出这个异常:
就会找到 Tomcat 线程池的 174 行:
然后你打上一个断点,玩去吧。
那我们刚刚说的,可以在门口等一分钟再进坑是怎么回事呢?
我们把参数告诉线程池就可以了,比如下面这样:
然后再去运行,因为队列满了后,触发拒绝异常,然后等 3 秒再去提交任务。而我们提交的一个任务 2 秒就能被执行完。
所以,这个场景下,所有的任务都会被正常执行。
现在你知道为了把你给它的任务尽量快速、全部的执行完成,Tomcat有多努力了吗?
小彩蛋
在看 Tomcat 自定义队列的时候我发现了作者这样的注释:
这个地方作用是把 forcedRemainingCapacity 参数设置为 0。
这个参数是在什么时候设置的呢?
就是下面这个关闭方法的时候:
org.apache.tomcat.util.threads.ThreadPoolExecutor#contextStopping
可以看到,调用 setCorePoolSize 方法之前,作者直接把 forcedRemainingCapacity 参数设置为了 0。
注释上面写的原因是JDK ThreadPoolExecutor.setCorePoolSize 方法会去检查 remainingCapacity 是否为 0。
至于为什么会去做这样的检查,Tomcat 的作者两次表示:I don't see why。I did not understand why。
so,他 fake 了 condition。
总之就是他说他也不明白为什么JDK 线程池 setCorePoolSize 方法调小核心线程池的时候要的限制队列剩余长度为 0 ,反正这样写就对了。
别问,问就是规定。
于是我去看了 JDK 线程池的 setCorePoolSize 方法,发现这个限制是在 jdk 1.6 里有,1.6 之后的版本对线程池进行了大规模的重构,取消了这个限制:
那 Tomcat 直接设置为 0 会带来什么问题呢?
正常的逻辑是队列剩余大小 = 队列长度 - 队列里排队的任务数。
而当你对其线程池(队列长度为300)进行监控的时候正常情况应该是这样:
但是当你调用 contextStopping 方法后可能会出现这样的问题:
很明显不符合上面的算法了。
好了,如果你们以后需要对 Tomcat 的线程池进行监控,且 JDK 版本在 1.6版本以上。那你可以去掉这个限制,以免误报警。
好了,恭喜你,朋友。又学到了一个基本用不上的知识点,奇怪的知识又增加了一点点。
Dubbo 线程池
这里再扩展一个 Dubbo 的线程池实现。
org.apache.dubbo.common.threadpool.support.eager.EagerThreadPoolExecutor
你可以看一下,思想还是这个思想:
但是 execute 方法有点不一样:
从代码上看,这里放入失败之后又立马调了一次 offer 方法,且没有等待时间。
也就是说两次 offer 的间隔是非常的短的。
其实我不太明白为什么这样去写,可能是作者留着口子好扩展吧?
因为如果这样写,为什么不直接调用这个方法呢?
java.util.concurrent.LinkedBlockingQueue#offer(E)
也是作者是想在极短的时间能赌一下吧?谁知道呢?
然后可以发现该线程池在拒绝策略上也做了很大的文章:
可以看到日志打印的非常详尽,warn 级别:
dumpJStack 方法,看名字也知道它是要去 Dump 线程了,保留现场:
在这个方法里面,他用了 JDK 默认的线程池,去异常 Dump 线程。
等等,阿里开发规范不是说了不建议用默认线程池吗?
其实这个规范看你怎么去拿捏。在这个场景下,用自带的线程池就能满足需求了。
而且你看第二个红框:提交之后就执行了 shutdown 方法,上面还给了个贴心警告。
必须要 shutdown 线程池,不然会导致 OOM。
这就是细节呀,朋友们。魔鬼都在细节里!
这里为什么用的 shutdown 不是 shutdownNow ?他们的区别是什么?为什么不调用 shutdown 方法会 OOM?
知识点呀,朋友们,都是知识点啊!
好了,到这里本文的分享也到了尾声。
以后当面试官问你 JDK 线程池的运行流程的时候,你答完之后画风一转,再来一个:
其实我们也可以先把最大线程数用完,然后再让任务进入队列。通过自定义队列,重写其 offer 方法就可以实现。目前我知道的 Tomcat 和 Dubbo 都提供了这样策略的线程池。
轻描淡写之间又装了一个漂亮逼。让面试官能进入下一个知识点,让你能更多的展现自己。
最后说一句(求关注)
本文主要介绍了 Tomcat 线程池的运行流程,和 JDK 线程池的流程比起来,它确实不一样。
而 Tomcat 线程池为什么要这样做呢?
其实就是因为 Tomcat 处理的多是 IO 密集型任务,用户在前面等着响应呢,结果你明明还能处理,却让用户的请求入队等待?
这样不好,不好。
说到底,又回到了任务类型是 IO 密集型还是 CPU 密集型这个话题上来。
有兴趣的可以看看我的这篇文章:《如何设置线程池参数?美团给出了一个让面试官虎躯一震的回答。》
点个赞吧,周更很累的,不要白嫖我,需要一点正反馈。
感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。
我是why技术,一个不是大佬,但是喜欢分享,又暖又有料的四川好男人。
-
tomcat只能在局域网用吗?_每天都在用,但你知道 Tomcat 的线程池有多努力吗?(建议收藏)...
2020-12-14 05:52:47让文章的温度更多一点点。上面的图是我在一次跑步的过程中拍的。活动之前赛事方搞了个留言活动,收集每公里路牌的一个宣传语。我的留言有幸被选中了:没人知道你在坚持什么,但你自己心里应该清楚。是在说跑马拉松,...大家好,我是 why,一个四川程序猿,成都好男人。
技术分享之前先简短的荒腔走板聊聊生活。让文章的温度更多一点点。
上面的图是我在一次跑步的过程中拍的。活动之前赛事方搞了个留言活动,收集每公里路牌的一个宣传语。我的留言有幸被选中了:
没人知道你在坚持什么,但你自己心里应该清楚。是在说跑马拉松,也是在说其他的事情。
我记得那天的太阳,骄阳似火,路上的树荫也非常的少。苦就苦在我还报的是超级马拉松(说是超级马拉松,其实就是一个全马 42 km加最后 3 km纯上坡的马拉松)到底有多晒,我给你看一下对比:
酷暑难耐,以至于 30 公里左右的地方我的心里出现了两个小人:
一个说:我好累啊,我跑不动了,我要退赛。
一个说:好呀好呀,我也好晒啊,退赛退赛。
我说:呸,看你们两个不争气的东西,让我带你去终点
于是在 36 公里的地方碰到了我提交的标语,非常开心,停下来拍了几张照片。给自己说:坚持不住的时候再坚持一下。最后的 3 公里上坡,抽筋了不知道多少次。远远看见终点拱门的时候我突然想到了在敦煌的时候悟出的一句话:自己给自己的辛苦,不是辛苦,是幸福。
好了,说回文章。
违背自觉的JDK线程池
先用 JDK 线程池来开个题。
问:这是一个自定义线程池,假设这个时候来了 100 个比较耗时的任务,请问有多少个线程在运行?
正确回答在之前的文章中回答了,这里不在赘述。
但是我面试的时候曾经遇到过很多对于 JDK 线程池不了解的朋友。
而这些人当中大多数都有一个通病,那就是遇到不太会的问题,那就去猜。
面试者遇到这个不会的题的时候,表面上微微一笑,实际上我都已经揣摩出他们的内心活动了:
MD,这题我没背过呀,但是刚刚听面试官说核心线程数是 10,最大线程数是 30。看题也知道答案不是 10 就是 30。选择题,百分之 50 的命中率,难道不赌一把?
等等,30 是最大线程数?最大?我感觉就是它了。
于是在电光火石一瞬间的思考后,和我对视起来,自信的说:
于是我也是微微一笑,告诉他:下去再了解一下吧,我们聊聊别的。
确实,如果完全不了解 JDK 线程池运行规则,按照直觉来说,我也会觉得应该是,不管是核心还是最大线程数,有任务来了应该先把线程池里面可用的线程用完了,然后再把任务提交到队列里面去排队。
可惜 JDK 的线程池,就是反直觉的。
那有符合我们直觉的线程池吗?
有的,你经常用的的 Tomcat ,它里面的线程池的运行过程就是先把最大线程数用完,然后再提交任务到队列里面去的。
我带你剖析一下。
Tomcat线程池
先打开 Tomcat 的 server.xml 看一下:
眼熟吧?哪一个学过 java web 的人没有配置过这个文件?哪一个配置过这个文件的人没有留意过 Executor 配置?
具体的可配置项可以查看官方文档:
http://tomcat.apache.org/tomcat-9.0-doc/config/executor.html
同时我找到一个可配置的参数的中文说明如下:
注意其中的第一个参数是 className,图片中少了个字母 c。
然后还有两个参数没有介绍,我补充一下:
- prestartminSpareThreads:boolean 类型,当服务器启动时,是否要创建出最小空闲线程(核心线程)数量的线程,默认值为 false 。
- threadRenewalDelay:long 类型,当我们配置了 ThreadLocalLeakPreventionListener 的时候,它会监听一个请求是否停止。当线程停止后,如果有需要,会进行重建,为了避免多个线程,该设置可以检测是否有 2 个线程同时被创建,如果是,则会按照该参数,延迟指定时间创建。如果拒绝,则线程不会被重建。默认为 1000 ms,设定为负值表示不更新。
我们主要关注 className 参数,如果不配置,默认实现是:
org.apache.catalina.core.StandardThreadExecutor
我们先解读一下这个方法(注意,本文中 Tomcat 源码版本号为:10.0.0-M4):
org.apache.catalina.core.StandardThreadExecutor#startInternal
从 123 行到 130 行,就是构建 Tomcat 线程池的地方,很关键,我解读一下:
123行
taskqueue = new TaskQueue(maxQueueSize);
创建一个 TaskQueue 队列,这个队列是继承自 LinkedBlockingQueue 的:
该队列上的注释值得关注一下:
主要是说这是一个专门为线程池设计的一个任务队列。配合线程池使用的时候和普通队列有不一样的地方。同时传递了一个队列长度,默认为 Integer.MAX_VALUE:
124行
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
构建一个 ThreadFactory,其三个入参分为如下:
- namePrefix:名称前缀。可以指定,其默认是“tomcat-exec-”。
- daemon:是否以守护线程模式启动。默认是 true。
- priority:线程优先级。是一个 1 到 10 之前的数,默认是 5。
125行
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
构建线程池,其 6 个入参分别如下:
这个具体含义我就不解释了,和 JDK 线程池是一样的。
只是给大家看一下默认参数。
另外还需要十分注意的一点是,这里的 ThreadPoolExecuteor 是 Tomcat 的,不是 JDK 的,虽然名字一样。
看一下 Tomcat 的 ThreadPoolExecuteor注释,里面提到了两个点,一是已提交总数,二是拒绝策略。后面都会讲到。
126行
executor.setThreadRenewalDelay(threadRenewalDelay);
设置 threadRenewalDelay 参数。不是本文重点,可以先不关心。
127 - 129行
if (prestartminSpareThreads) { executor.prestartAllCoreThreads();}
设置是否预启动所有的核心线程池,这个参数在之前文章中也有讲到过。
prestartminSpareThreads 参数默认是 false。但是我觉得这个地方你设置为 true 也是多次一举。完全没有必要。
为什么呢?
因为在 125 行构建线程池的时候已经调用过这个方法了:
从源码可以看出,不管你调用哪一个线程池构造方法,都会去调用 prestartAllCoreThreads 方法。
所以,这算不算 Tomcat 的一个小 Bug 呢?快拿起你的键盘给它提 pr 吧。
130行
taskqueue.setParent(executor);
这行代码非常关键。没有这行代码,Tomcat 的线程池则会表现的和 JDK 的线程池一样。
拿下面的程序举例:
自定义线程池最多可以容纳 150+300 个任务。
当 24 行注释的时候,Tomcat 线程池运行的过程和 JDK 线程池的运行过程一样,运行的线程数只会是核心程序数 5。
当 24 行取消注释的时候,Tomcat 线程池就会一直创建线程个数到 150 个,然后把剩下的任务提交到自定义的 TaskQueue 队列里面去。
我再提供一个复制粘贴直接运行版本,你分别运行一下,试一试,看看结果:
public class TomcatThreadPoolExecutorTest { public static void main(String[] args) throws InterruptedException { String namePrefix = "why不止技术-exec-"; boolean daemon = true; TaskQueue taskqueue = new TaskQueue(300); TaskThreadFactory tf = new TaskThreadFactory(namePrefix, daemon, Thread.NORM_PRIORITY); ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 150, 60000, TimeUnit.MILLISECONDS, taskqueue, tf); //taskqueue.setParent(executor); for (int i = 0; i { logStatus(executor, "创建任务"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }); } catch (Exception e) { e.printStackTrace(); } } Thread.currentThread().join(); } private static void logStatus(ThreadPoolExecutor executor, String name) { TaskQueue queue = (TaskQueue) executor.getQueue(); System.out.println(Thread.currentThread().getName() + "-" + name + "-:" + "核心线程数:" + executor.getCorePoolSize() + "活动线程数:" + executor.getActiveCount() + "最大线程数:" + executor.getMaximumPoolSize() + "总任务数:" + executor.getTaskCount() + "当前排队线程数:" + queue.size() + "队列剩余大小:" + queue.remainingCapacity()); }}
接着就去分析这行代码的用途,看看这一行代码,怎么就反转了 JDK 线程池的运行过程。
源码之下无秘密
如果你对 JDK 线程池的源码熟悉一点的话,你大概能猜到 Tomcat 肯定是在控制新建线程的地方做了手脚,也就是下面这个地方:
PS:需要说明一下的是,上面的截图是 JDK 线程池的 execute 方法。因为 Tomcat 线程池的提交也是复用的这个方法。但是 workQueue 不是同一个队列。
那你先把工作流程和各个参数都摸熟了,然后写个 Demo ,接着去疯狂的 Debug 吧。然后你总会找到这个地方的,而且你会发现,不难找。
好了,上面主要关注我圈起来的部分。
在截图的 1371 行,如果没有把任务成功放到队列里面(前提是线程池是运行状态),则会执行 1378 行的逻辑,而这个逻辑,就是创建非核心线程的逻辑。
所以,经过上面的推导之后,一切都清晰了,Tomcat 只需要在自定义队列的 offer 方法中做文章即可。
所以,我们重点关注一下该方法:
org.apache.tomcat.util.threads.TaskQueue#offer
为了更加直观的看出来其运行流畅,我在第 80 行打了个断点运行程序如下:
可以看到里面的几个参数,下面的讲解会用到这里面的参数:
第一个 if 判断
首先第一个 if,判断 parent 是否为空:
从断点运行参数截图可以看出,这里的 parent 就是 Tomcat 的 ThreadPoolExecutor 类。
当 parent 为 null 时,直接调用原始的 offer 方法。
所以,还记得我前面说的吗?
现在你知道为什么了吧?
源码,就是这个源码。道理,就是这么个道理。
所以,这里不为空,不满足条件,进入下一个 if 判断。
第二个 if 判断
首先,需要明确的是,能进入到第二个判断的时候,当前运行中的线程数肯定是大于等于核心线程数(因为已经在执行往队列里面放的逻辑了,说明核心线程数肯定是满了),小于最大线程数的。
其中 getPoolSize 方法是获取线程池中当前运行的线程数量:
所以,第二个 if 判断的是运行中的线程数是否等于最大线程数。如果等于,说明所有线程都在工作了,把任务扔到队列里面去。
从断点运行参数截图可以看到, 当前运行数为 5 ,最大线程数为 150。不满足条件,进入下一个 if 判断。
第三个 if 判断
首先我们看看 getSubmittedCount 获取的是个什么玩意:
getSubmittedCount 获取的是当前已经提交但是还未完成的任务的数量,其值是队列中的数量加上正在运行的任务的数量。
从断点运行参数截图可以看到,当前情况下该数据为 6。而 parent.getPoolSize() 为 5。
不满足条件,进入下一个 if 判断。
但是这个地方需要多说一句的是,如果当已经提交但是还未完成的任务的数量小于线程池中运行线程的数量时,Tomcat 的做法是把任务放到队列里面去,而不是立即执行。
其实这样想来也是很符合逻辑且简单的做法的。
反正有空闲的线程嘛,扔到队列里面去就被空闲的线程消费了。又何必立即执行呢?破坏流程不说,还需要额外实现。
出力不讨好。没必要。
第四个 if 判断
这个判断就很关键了。
如果当前运行中的线程数量小于最大线程数,返回 false。
注意哦,前面的几个 if 判断都是不满足条件就放入队列哦。而这里是不满足条件,就返回 false。
返回 false 意味着什么?
意味着要执行 1378 行代码,去创建线程了呀。
所以,整个流程图大概就是这样:
再聊聊拒绝策略
拒绝策略需要看这个方法:
org.apache.tomcat.util.threads.ThreadPoolExecutor#execute(java.lang.Runnable, long, java.util.concurrent.TimeUnit)
看一下该方法上的注释:
如果队列满了,则会等待指定时间后再次放入队列。
如果再次放入队列的时候还是满的,则抛出拒绝异常。
这个逻辑就类似于你去上厕所,发现坑位全都被人占着。这个时候你的身体告诉你,你括弧肌最多还能在忍一分钟。
于是,你掐着表在门口,深呼吸,闭眼冥想,等了一分钟。
运气好的,再去一看:哎,有个空的坑位了,赶紧占着。
运气不好,再去一看:哎,还是没有位置,怎么办呢?抛异常吧。具体怎么抛就不说了,自行想象。
所以我们看看这个地方,Tomcat 的代码是怎么实现的:
catch 部分首先判断队列是不是 Tomcat 的自定义队列。如果是,则进入这个 if 分支。
关键的逻辑就在这个 if 判断里面了。
可以看到 172 行:
if (!queue.force(command, timeout, unit))
调用了队列的 force 方法。我们知道 BlockingQueue 是没有 force 方法的。
所以这个force 是 Tomcat 自定义队列特有的方法:
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException { if (parent == null || parent.isShutdown()) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning")); //forces the item onto the queue, to be used if the task is rejected return super.offer(o,timeout,unit);}
进去一看发现:害,也不过如此嘛。就是对原生 offer 方法的一层包装而已。
如果成功加入队列则万事大吉,啥事没有。
如果没有成功加入队列,则抛出异常,并维护 submittedCount 参数。
前面说过:submittedCount 参数 = 队列中的任务个数 + 正在运行的任务数。
所以,这里需要进行减一操作。
拒绝策略就说完了。
但是这个地方的源码是我带你找到的。如果你想自己找到应该怎么操作呢?
你想啊,你是想测试拒绝策略。那只要触发其拒绝策略就行了。
比如下面这样:
给一个只能容纳 450 个任务的线程池提交 500 个任务。
然后就会抛出这个异常:
就会找到 Tomcat 线程池的 174 行:
然后你打上一个断点,玩去吧。
那我们刚刚说的,可以在门口等一分钟再进坑是怎么回事呢?
我们把参数告诉线程池就可以了,比如下面这样:
然后再去运行,因为队列满了后,触发拒绝异常,然后等 3 秒再去提交任务。而我们提交的一个任务 2 秒就能被执行完。
所以,这个场景下,所有的任务都会被正常执行。
现在你知道为了把你给它的任务尽量快速、全部的执行完成,Tomcat有多努力了吗?
小彩蛋
在看 Tomcat 自定义队列的时候我发现了作者这样的注释:
这个地方作用是把 forcedRemainingCapacity 参数设置为 0。
这个参数是在什么时候设置的呢?
就是下面这个关闭方法的时候:
org.apache.tomcat.util.threads.ThreadPoolExecutor#contextStopping
可以看到,调用 setCorePoolSize 方法之前,作者直接把 forcedRemainingCapacity 参数设置为了 0。
注释上面写的原因是JDK ThreadPoolExecutor.setCorePoolSize 方法会去检查 remainingCapacity 是否为 0。
至于为什么会去做这样的检查,Tomcat 的作者两次表示:I don't see why。I did not understand why。
so,他 fake 了 condition。
总之就是他说他也不明白为什么JDK 线程池 setCorePoolSize 方法调小核心线程池的时候要的限制队列剩余长度为 0 ,反正这样写就对了。
别问,问就是规定。
于是我去看了 JDK 线程池的 setCorePoolSize 方法,发现这个限制是在 jdk 1.6 里有,1.6 之后的版本对线程池进行了大规模的重构,取消了这个限制:
那 Tomcat 直接设置为 0 会带来什么问题呢?
正常的逻辑是队列剩余大小 = 队列长度 - 队列里排队的任务数。
而当你对其线程池(队列长度为300)进行监控的时候正常情况应该是这样:
但是当你调用 contextStopping 方法后可能会出现这样的问题:
很明显不符合上面的算法了。
好了,如果你们以后需要对 Tomcat 的线程池进行监控,且 JDK 版本在 1.6版本以上。那你可以去掉这个限制,以免误报警。
好了,恭喜你,朋友。又学到了一个基本用不上的知识点,奇怪的知识又增加了一点点。
Dubbo 线程池
这里再扩展一个 Dubbo 的线程池实现。
org.apache.dubbo.common.threadpool.support.eager.EagerThreadPoolExecutor
你可以看一下,思想还是这个思想:
但是 execute 方法有点不一样:
从代码上看,这里放入失败之后又立马调了一次 offer 方法,且没有等待时间。
也就是说两次 offer 的间隔是非常的短的。
其实我不太明白为什么这样去写,可能是作者留着口子好扩展吧?
因为如果这样写,为什么不直接调用这个方法呢?
java.util.concurrent.LinkedBlockingQueue#offer(E)
也是作者是想在极短的时间能赌一下吧?谁知道呢?
然后可以发现该线程池在拒绝策略上也做了很大的文章:
可以看到日志打印的非常详尽,warn 级别:
dumpJStack 方法,看名字也知道它是要去 Dump 线程了,保留现场:
在这个方法里面,他用了 JDK 默认的线程池,去异常 Dump 线程。
等等,阿里开发规范不是说了不建议用默认线程池吗?
其实这个规范看你怎么去拿捏。在这个场景下,用自带的线程池就能满足需求了。
而且你看第二个红框:提交之后就执行了 shutdown 方法,上面还给了个贴心警告。
必须要 shutdown 线程池,不然会导致 OOM。
这就是细节呀,朋友们。魔鬼都在细节里!
这里为什么用的 shutdown 不是 shutdownNow ?他们的区别是什么?为什么不调用 shutdown 方法会 OOM?
知识点呀,朋友们,都是知识点啊!
好了,到这里本文的分享也到了尾声。
以后当面试官问你 JDK 线程池的运行流程的时候,你答完之后画风一转,再来一个:
其实我们也可以先把最大线程数用完,然后再让任务进入队列。通过自定义队列,重写其 offer 方法就可以实现。目前我知道的 Tomcat 和 Dubbo 都提供了这样策略的线程池。
轻描淡写之间又装了一个漂亮逼。让面试官能进入下一个知识点,让你能更多的展现自己。
来源:https://mp.weixin.qq.com/s?__biz=MzIxNTQ4MzE1NA==&mid=2247485707&idx=1&sn=237055cf8fe1c2674040a5b1cd65f1f5&utm_source=tuicool&utm_medium=referral
来源于why不止技术 ,作者why技术
-
tomcat6的项目能直接在tomcat7上用吗_谁推荐个好用的第三方便签APP,电脑上用的...
2020-11-27 17:20:09电脑上用的桌面便签小工具有很多,比如系统便笺,再比如浏览器或是某些应用程序中的便签插件等,也有可以直接下载的便签小程序,甚至是一些便签小工具的网页版也能记便签。电脑上可以用的便签小工具有这么多,哪一款...电脑上用的桌面便签小工具有很多,比如系统便笺,再比如浏览器或是某些应用程序中的便签插件等,也有可以直接下载的便签小程序,甚至是一些便签小工具的网页版也能记便签。
电脑上可以用的便签小工具有这么多,哪一款比较好用值得推荐给大家呢?电脑上有什么好用的第三方便签APP值得推荐吗?
这篇文章的主要内容,小编就跟大家分享一款电脑上用的深受上班族喜爱的桌面小便签,为大家介绍下商务办公云便签敬业签都有哪些实用的小功能。
以Windows电脑为例,电脑设备上可以下载安装桌面版便签程序或是直接登录网页版使用,PC端和web端登录同一个便签账号和密码,便签数据支持自动云同步,对于安卓手机设备、苹果手机设备等,也能免费安装便签app与电脑版同步,电脑手机多端云同步是这款桌面工作便签小工具的亮点功,市面上很少有这样的多端自动、免费、实时云同步的云便签。
对于使用过电脑系统便笺小工具的网友一定深有体会,电脑系统便笺无法添加时间提醒,只具备简单的记事功能,将需要备忘的事情记录到上面之后,无法添加时间定时通知,为此,只得寻求可以时间提醒的第三方便签软件。
而在敬业云便签上,记事和提醒都是基础免费功能,将事情记录到便签中,不仅可以添加指定时间提醒,还能设置重复周期,标注重要事件提醒等,另外便签中的提醒到期后,还能借助手机微信公众号、企业微信群助手、手机来电响铃、手机短信消息通知等方式同步推送消息提醒,多层次、全方位地解决备忘事件的提醒问题。
想要了解更多关于商务云便签敬业签的功能教程与使用体验,不如现在下载安装体验吧https://www.jingyeqian.com/xiazai/
-
tomcat6的项目能直接在tomcat7上用吗_鸡蛋托就能种菜,这么好的东西扔了就是浪费!...
2020-12-04 11:49:10每天为你更新不同的手工艺术,编织,刺绣,串珠,布艺,皮艺,剪纸,折纸,钩编精品,粘土软陶,国内外手工艺术等精彩文章,在这里愿与您共同分享,QQ号:88461358只知道家里没地种菜了就用泡沫箱或各种的花盆,但是...每天为你更新不同的手工艺术,编织,刺绣,串珠,布艺,皮艺,剪纸,折纸,钩编精品,粘土软陶,国内外手工艺术等精彩文章,在这里愿与您共同分享,QQ号:88461358
只知道家里没地种菜了就用泡沫箱或各种的花盆,但是鸡蛋托做菜你试过吗?不需要太多的土,只要能覆盖鸡蛋托的高度,就能把菜种好,那些长得快又高的菜苗,特别适合种在鸡蛋托上,除了能保水保湿,还可以锁住土里的营养成份。每棵苗都能有规律地形成一个属于自己的空间,不会轻松被抢走多余的养份,省时也省力,不会种菜的也可以试试。用蛋托来试试种植小萝卜,准备木条,钉一个架子,家里有阳台的也可以试一下,或有院子,地不大, 也可以这样分层管理,上面种菜,下面可以放杂物。或直接做一个浅盘状的大花盆,能放置鸡蛋托的就可以。
上面再用木条围边,用于放置鸡蛋托。
架子完成,先在上面铺上一层防水布。
之后覆土,撒上蛋壳粉做底肥,也可以直接买底肥代替。
这里插播一下蛋壳粉的做法,蛋壳洗干净,倒扣晾干。
之后在蛋壳上用竹签或筷子戳个洞,用绳子一个个串起来晾晒。
晾晒完后,用粉碎机或破壁机打成粉,就是蛋壳粉了,以后种菜种花种菜都可以用。
也可以不打成粉,把蛋壳捣碎撒在土的表面当肥料。
打成粉吸收会更快一点。
回到正题,蛋壳粉撒好后用铲子翻拌均匀。
铺上蛋托
覆土
浇水
用筷子或竹签在蛋托的每个洞上戳个小孔。之后在小孔上放进几颗小萝卜的种子。
把小孔覆盖好
浇水
16天后,就长出新芽了。
之后筛选掉长得特别小的苗
日常养护,等30天后,萝卜就能收成了。
翠绿的萝卜苗剪下来可以用盐腌成酸菜,或用水焯一下切粒,加姜蒜炒成一盘美味的菜。切下来的小萝卜,就能发挥你的厨艺做出百种吃法。
戳视频查看详细教程
▼
-
前端的请求最大线程数是多少啊_每天都在用,但你知道 Tomcat 的线程池有多努力吗?
2020-12-01 23:03:19说点不一样的线程池执行策略和线程拒绝策略,探讨怎么让线程池先用完最大线程池再把任务放到队列中。荒腔走板大家好,我是 why,一个四川程序猿,成都好男人。先是本号的特色,技术分享之前先简短的荒腔走板聊聊生活... -
mac怎么安装tomcat_用了十几年的Tomcat,你真的了解它吗?阿里大师带你全方位解析...
2020-11-25 18:37:28对于一个最简单的Web项目架构来说,Tomcat是前军,SSM是中军,Mysql是后方,而我们通常非常重视SSM框架的学习,而忽略Tomcat,如果能够多懂得关于Tomcat的底层原理知识,那么对于日常开发工作或项目性能调优是非常... -
tomcat service.xml能多个context吗_Java进阶:Tomcat干货笔记
2020-12-05 08:28:00用Java编写的Servlet、jsp程序需要部署在Web容器上,才能实现功能。Tomcat体系结构一、Connector和Container1.Tomcat有两个功能:●和客户端浏览器进行交互,进行socket通信,将字节流和Reques... -
tomcat只能在局域网用吗?_自媒体作图只能用PS吗?不,这3个作图工具很好用
2020-12-10 02:46:51不止有PS,每个人的爱好和需求都不一样,现在不管是做图工具还是软件都有很多。像图怪兽、稿定设计、创客贴这几个都是不错的做图工具,适合不懂PS的小白使用。下面就讲一下它们的介绍与用途吧。一、图怪兽是一个在线... -
7000人的企业使用的OA系统,可以使用tomcat做应用服务器吗
2009-09-09 11:31:18<br />问题补充:</strong><br />还有一点就是Apache+Tomcat做LB是可以,但是我对于这种群集感觉是存在隐患的,有个问题不太好解决,如果tomcat下要生成独立的文件的,那么如何使多个tomcat实现资源的同步? -
eclips 配置一个tomcat,启动多个不同端口的web项目
2019-10-07 18:05:42前提: 记录这个文章是因为... 运动多个web 项目,设置不同的端口,需要多个tomcat吗 ,答案是不需要的,用eclips开发的同学,可以用下面操作,同时启来多个web服务,以tomcat8为列 示例: 1.eclips配置tom... -
tomcat多版本
2010-10-29 16:54:00Tomcat 的版本更新很快,各个版本也不尽相同,如果你现在使用的... 要想让不同的版本 Tomcat 并存於你的电脑上,第一个要求是,千万不要下载 tomcatxxx.exe 的安装程式,也就是说,Tomcat 不要用安装的,请 -
必须更换Tomcat的新版本以修补漏洞吗
2011-03-21 18:25:13<p><br>8080端口的Tomcat是用的Tomcat/6.0.20版本,另一个端口用的Tomcat/6.0.14版本,报的漏洞比这个还多些。 (;">我怀疑这个评估就是获取Tomcat版本后查询此版本已报出的漏洞给出的) <br>针对每个漏洞,给... -
Tomcat 的线程池实现原理
2021-02-02 12:55:38一个激进创建线程的弹性线程池更符合我们的需求,你能给出相关的实现吗?实现后再测试一下,是否所有的任务都可以正常处理完成呢? 既然选择先扩容线程池再加入队列,那为什么不干脆...不是也提倡不同的任务用不同的 -
请问大家,tomcat支持EL语言吗?
2007-12-08 10:57:13我看到网上很多文章都说。tomcat5是支持EL语言的。但是为什么我做的jsp页面,用${}输出不了东西呢? 比如我这样 ${s} 但是为什么这样我是输出不了的呢?... -
请教IIS与多个Tomcat整和的问题
2010-08-03 14:39:42因为一些原因,我们现在用4个tomcat分别部署了4个不同的工程,现在我们需要使用IIS来转发请求,来屏蔽掉访问端口。 请问下大家 我因该怎么做呢? 或者说有什么好的建议吗? 谢谢大家拉~ -
tomcat只能在局域网用吗?_供暖季家里只能全封闭吗?冬天开窗想要通风,却被指出过度用热!...
2020-12-12 18:31:50进入供暖季,家家户户都用上了地暖和暖气片,外面虽然很冷,但屋子里面还是洋洋的,别提多舒服了。虽然屋里暖气很舒服,但为了身体的健康,还是要经常打开窗户进行换气。然而最近热力公司却发布了这样一条公告,大概... -
Windows下同时启动多个免安装tomcat
2019-07-03 14:02:03做开发的都知道平时开发一个tomcat足够用啦,但是如果老板让你做一个web service 接口,这是你需要两个tomcat分辨启动进行调用,当然你也可以在一个tomcat下进行启动调试,但不如两个tomcat进行调试比较有正规感吗。... -
多tomcat如何共享一个域名配置
2009-12-17 19:59:34tomcat发布多个项目,请问有没有办法让多个tomcat调用一个host的配置,就是保持这多个tomcat的域名配置是一样的,请指点,谢谢 <strong>问题补充:</strong><br />我用的所有请求交给tomcat来处理。这样只需要... -
导入的maven项目为什么在tomcat跑不了呢????是jar包的原因吗
2017-02-09 07:25:02试了网上很多方法,还是出现这个问题,用了tomcat5-8都是这个问题,jdk5-7都换过了还是这个样子,有时间的帮忙看看呗 严重: Exception sending context initialized event to listener instance of class org.... -
tomcat生命周期的管理——生命周期统一接口Lifecycle
2014-11-04 21:27:49我们知道Tomcat的架构设计是清晰的、模块化的,其拥有很多组件,假如我们要启动Tomcat,可以一个一个启动组件,但这样启动有很多缺点,不仅麻烦,而且容易漏了组件启动,还会对后面动态组件扩展带来麻烦。... -
tomcat和weblogicn能同时运行吗_空气开关和漏电开关能同时装吗
2020-11-22 12:59:02在一个配电箱内,根据不同电器有的需要用漏电开关控制,有的不一定要用漏电开关用空气开关控制即可,因此两种开关同时装或叫混合装比较常见。或者总开关与分路开关之间,前者用空气开关后者用漏电开关,反过来前者用... -
一个困扰多时的tomcat运行一段时间死掉的问题,急
2009-04-12 22:06:00我的hibernate的dao之类的用的是myeclipse默认生成的,里面有好多log4j的日志代码,和这个有关吗?而且那个默认生成的baseHibernateDao里面有关session的代码是不是有问题?老师给我改成单例模式才好用。开始的时候... -
将多个Tomcat实例应用转为Windows服务
2011-10-04 12:07:26是的,没错,但不知道你是 否将N个应用都用一个Tomcat实例来服务呢?这样的弊端是明显的,某个应用出现问题,需要重新启动Tomcat服务,那势必会影响其他正常运行的N- 1个服务,这样的效果显然不尽人意。理想的情况是... -
一个系统把端口区别开就可以装多个tomcat了吧?
2019-09-15 16:27:47用nginx来做转发对外都是80端口的里面可以装多个tomcat如果不用nginx安装多个tomcat就要指定端口 那多个tomcat都是8080端口吗? 这个不可能啊,端口只能被一个应用程勋使用,启动会报端口被占用 哦,我明白了,每个... -
得到Tomcat连接池的当前连接数
2013-09-30 11:20:07关于如何配置Tomcat数据库连接池,网上已有太多文章了。可是找不到一篇文章能 告诉我,怎么能得到Tomcat... 先是看了Tomcat的相关源码,得到它用了jakarta commons-dbcp这个包,又下载了 commons-dbcp这个包的源吗 -
Tomcat连接池的当前连接数
2010-03-02 22:55:00关于如何配置Tomcat数据库连接池,网上已有太多文章了。可是找不到一篇文章能... 先是看了Tomcat的相关源码,得到它用了jakarta commons-dbcp这个包,又下载了commons-dbcp这个包的源吗。终于在BasicDataSource找... -
在linux下tomcat5.0.28的安全问题
2008-06-29 23:02:38现在我不明白的是这个war文件是怎样传上来的,因为系统管理员把8080端品没关掉,我们是用的8009和apache配合就可以的,里面有admin和manager这两个目录没有去掉,但奇怪的是我记得以前我默认的tomcat里面的tomcat-users.... -
tomcat 下多个项目共用jar包 ,打包成war,部署时候出错了
2017-05-12 08:14:11Tomcat下面有多个项目,内存不够,jar包基本都是一样的,那么想说共用jar包 ,在Tomcat的catalina下面也设置了shared.loader=${catalina.base}/shared/lib,${catalina.base}/shared/lib/*.jar。用maven打包项目的...