精华内容
下载资源
问答
  • 说点不一样线程池执行策略和线程拒绝策略,探讨怎么让线程池先完最大线程池再把任务放到队列中。 荒腔走板 大家好,我是 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技术,一个不是大佬,但是喜欢分享,又暖又有料的四川好男人。

    展开全文
  • 让文章温度更一点点。上面图是我在一次跑步过程中拍。活动之前赛事方搞了个留言活动,收集每公里路牌一个宣传语。我留言有幸被选中了:没人知道你在坚持什么,但你自己心里应该清楚。是在说跑马拉松,...
    46d494ceff3f2efde33029a16d06bc23.png

    大家好,我是 why,一个四川程序猿,成都好男人。

    技术分享之前先简短的荒腔走板聊聊生活。让文章的温度更多一点点。

    ef657c0fbc4feee9cbcae4e42c3d0acd.png

    上面的图是我在一次跑步的过程中拍的。活动之前赛事方搞了个留言活动,收集每公里路牌的一个宣传语。我的留言有幸被选中了:

    没人知道你在坚持什么,但你自己心里应该清楚。是在说跑马拉松,也是在说其他的事情。

    我记得那天的太阳,骄阳似火,路上的树荫也非常的少。苦就苦在我还报的是超级马拉松(说是超级马拉松,其实就是一个全马 42 km加最后 3 km纯上坡的马拉松)到底有多晒,我给你看一下对比:

    3c1c6914b53303474b1b643b18953d78.png

    酷暑难耐,以至于 30 公里左右的地方我的心里出现了两个小人:

    一个说:我好累啊,我跑不动了,我要退赛。

    一个说:好呀好呀,我也好晒啊,退赛退赛。

    我说:呸,看你们两个不争气的东西,让我带你去终点

    于是在 36 公里的地方碰到了我提交的标语,非常开心,停下来拍了几张照片。给自己说:坚持不住的时候再坚持一下。最后的 3 公里上坡,抽筋了不知道多少次。远远看见终点拱门的时候我突然想到了在敦煌的时候悟出的一句话:自己给自己的辛苦,不是辛苦,是幸福。

    好了,说回文章。

    违背自觉的JDK线程池

    先用 JDK 线程池来开个题。

    8ba3d851321b1434a01f098f80b20100.png

    问:这是一个自定义线程池,假设这个时候来了 100 个比较耗时的任务,请问有多少个线程在运行?

    正确回答在之前的文章中回答了,这里不在赘述。

    但是我面试的时候曾经遇到过很多对于 JDK 线程池不了解的朋友。

    而这些人当中大多数都有一个通病,那就是遇到不太会的问题,那就去猜。

    面试者遇到这个不会的题的时候,表面上微微一笑,实际上我都已经揣摩出他们的内心活动了:

    MD,这题我没背过呀,但是刚刚听面试官说核心线程数是 10,最大线程数是 30。看题也知道答案不是 10 就是 30。选择题,百分之 50 的命中率,难道不赌一把?

    等等,30 是最大线程数?最大?我感觉就是它了。

    于是在电光火石一瞬间的思考后,和我对视起来,自信的说:

    7de192331ec3dbcaced2871c641797b4.png

    于是我也是微微一笑,告诉他:下去再了解一下吧,我们聊聊别的。

    2b10ed574e68f5b39f7a5717663836a3.gif

    确实,如果完全不了解 JDK 线程池运行规则,按照直觉来说,我也会觉得应该是,不管是核心还是最大线程数,有任务来了应该先把线程池里面可用的线程用完了,然后再把任务提交到队列里面去排队。

    可惜 JDK 的线程池,就是反直觉的。

    那有符合我们直觉的线程池吗?

    有的,你经常用的的 Tomcat ,它里面的线程池的运行过程就是先把最大线程数用完,然后再提交任务到队列里面去的。

    我带你剖析一下。

    Tomcat线程池

    先打开 Tomcat 的 server.xml 看一下:

    16fc4123fcee10d1f8d980be2a6f8576.png

    眼熟吧?哪一个学过 java web 的人没有配置过这个文件?哪一个配置过这个文件的人没有留意过 Executor 配置?

    具体的可配置项可以查看官方文档:

    http://tomcat.apache.org/tomcat-9.0-doc/config/executor.html

    同时我找到一个可配置的参数的中文说明如下:

    fb2d4884a7957abf5101ef9fac428350.png

    注意其中的第一个参数是 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
    d0a40e3a6c8de8299929947dd221a68f.png

    从 123 行到 130 行,就是构建 Tomcat 线程池的地方,很关键,我解读一下:

    123行

    taskqueue = new TaskQueue(maxQueueSize);

    创建一个 TaskQueue 队列,这个队列是继承自 LinkedBlockingQueue 的:

    5fd84d9ccf8fa8f277a36d6261728c72.png

    该队列上的注释值得关注一下:

    主要是说这是一个专门为线程池设计的一个任务队列。配合线程池使用的时候和普通队列有不一样的地方。同时传递了一个队列长度,默认为 Integer.MAX_VALUE:

    c9f845db1a6d6078edf390823472fe2c.png

    124行

    TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());

    构建一个 ThreadFactory,其三个入参分为如下:

    abe85d4fb3c30986d544cda60017fbd6.png
    1. namePrefix:名称前缀。可以指定,其默认是“tomcat-exec-”。
    2. daemon:是否以守护线程模式启动。默认是 true。
    3. priority:线程优先级。是一个 1 到 10 之前的数,默认是 5。

    125行

    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);

    构建线程池,其 6 个入参分别如下:

    40e5a87774bb724925b97d7326e3d22e.png

    这个具体含义我就不解释了,和 JDK 线程池是一样的。

    只是给大家看一下默认参数。

    451ed79d217f7352e58f2a12cd163ca8.png

    另外还需要十分注意的一点是,这里的 ThreadPoolExecuteor 是 Tomcat 的,不是 JDK 的,虽然名字一样。

    a2043bf16cc46f6b086893d2d8b3c5d2.png

    看一下 Tomcat 的 ThreadPoolExecuteor注释,里面提到了两个点,一是已提交总数,二是拒绝策略。后面都会讲到。

    126行

    executor.setThreadRenewalDelay(threadRenewalDelay);

    设置 threadRenewalDelay 参数。不是本文重点,可以先不关心。

    127 - 129行

    if (prestartminSpareThreads) {    executor.prestartAllCoreThreads();}

    设置是否预启动所有的核心线程池,这个参数在之前文章中也有讲到过。

    prestartminSpareThreads 参数默认是 false。但是我觉得这个地方你设置为 true 也是多次一举。完全没有必要。

    为什么呢?

    因为在 125 行构建线程池的时候已经调用过这个方法了:

    a1c9dcb99c22251a02fedbd305c788df.png

    从源码可以看出,不管你调用哪一个线程池构造方法,都会去调用 prestartAllCoreThreads 方法。

    所以,这算不算 Tomcat 的一个小 Bug 呢?快拿起你的键盘给它提 pr 吧。

    130行

    taskqueue.setParent(executor);

    这行代码非常关键。没有这行代码,Tomcat 的线程池则会表现的和 JDK 的线程池一样。

    拿下面的程序举例:

    bdca4c7a4e45d4f5b40081b9bb47ce49.png

    自定义线程池最多可以容纳 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 肯定是在控制新建线程的地方做了手脚,也就是下面这个地方:

    937372d41d464374816c8b60d37046e4.png

    PS:需要说明一下的是,上面的截图是 JDK 线程池的 execute 方法。因为 Tomcat 线程池的提交也是复用的这个方法。但是 workQueue 不是同一个队列。

    4d7b52faddb30068f01acb8e7b303404.png

    那你先把工作流程和各个参数都摸熟了,然后写个 Demo ,接着去疯狂的 Debug 吧。然后你总会找到这个地方的,而且你会发现,不难找。

    好了,上面主要关注我圈起来的部分。

    在截图的 1371 行,如果没有把任务成功放到队列里面(前提是线程池是运行状态),则会执行 1378 行的逻辑,而这个逻辑,就是创建非核心线程的逻辑。

    所以,经过上面的推导之后,一切都清晰了,Tomcat 只需要在自定义队列的 offer 方法中做文章即可。

    所以,我们重点关注一下该方法:

    org.apache.tomcat.util.threads.TaskQueue#offer
    06dd206a10dca9ee28b2e107e8db2e29.png

    为了更加直观的看出来其运行流畅,我在第 80 行打了个断点运行程序如下:

    26f86d2fe7031e53fba34338fbb8ba02.png

    可以看到里面的几个参数,下面的讲解会用到这里面的参数:

    09da173e74d4c1ecdceb1e8a111306f7.png

    第一个 if 判断

    首先第一个 if,判断 parent 是否为空:

    9c2f345463f5efdc6540cba3c371dbdc.png

    从断点运行参数截图可以看出,这里的 parent 就是 Tomcat 的 ThreadPoolExecutor 类。

    当 parent 为 null 时,直接调用原始的 offer 方法。

    所以,还记得我前面说的吗?

    fc76da0ae16f67b3eeecc9319fab67bb.png

    现在你知道为什么了吧?

    源码,就是这个源码。道理,就是这么个道理。

    所以,这里不为空,不满足条件,进入下一个 if 判断。

    第二个 if 判断

    0aaaef6e2d9c179ae80501f9cad9aea4.png

    首先,需要明确的是,能进入到第二个判断的时候,当前运行中的线程数肯定是大于等于核心线程数(因为已经在执行往队列里面放的逻辑了,说明核心线程数肯定是满了),小于最大线程数的。

    其中 getPoolSize 方法是获取线程池中当前运行的线程数量:

    57d317437757902d80dacd2bfafd19df.png

    所以,第二个 if 判断的是运行中的线程数是否等于最大线程数。如果等于,说明所有线程都在工作了,把任务扔到队列里面去。

    从断点运行参数截图可以看到, 当前运行数为 5 ,最大线程数为 150。不满足条件,进入下一个 if 判断。

    第三个 if 判断

    caf2d31f0e5d25cec34a29bfebcdee60.png

    首先我们看看 getSubmittedCount 获取的是个什么玩意:

    5f992f473e6c8ad73149e2b12416d4ec.png

    getSubmittedCount 获取的是当前已经提交但是还未完成的任务的数量,其值是队列中的数量加上正在运行的任务的数量。

    从断点运行参数截图可以看到,当前情况下该数据为 6。而 parent.getPoolSize() 为 5。

    不满足条件,进入下一个 if 判断。

    但是这个地方需要多说一句的是,如果当已经提交但是还未完成的任务的数量小于线程池中运行线程的数量时,Tomcat 的做法是把任务放到队列里面去,而不是立即执行。

    其实这样想来也是很符合逻辑且简单的做法的。

    反正有空闲的线程嘛,扔到队列里面去就被空闲的线程消费了。又何必立即执行呢?破坏流程不说,还需要额外实现。

    出力不讨好。没必要。

    第四个 if 判断

    63e0380650fbf07d556b771a92a550fc.png

    这个判断就很关键了。

    如果当前运行中的线程数量小于最大线程数,返回 false。

    注意哦,前面的几个 if 判断都是不满足条件就放入队列哦。而这里是不满足条件,就返回 false。

    返回 false 意味着什么?

    61f990c133d83a00c73efe871b9e8b3b.png

    意味着要执行 1378 行代码,去创建线程了呀。

    所以,整个流程图大概就是这样:

    906911dc1fbf6c98b040473a8f1a2061.png

    再聊聊拒绝策略

    拒绝策略需要看这个方法:

    org.apache.tomcat.util.threads.ThreadPoolExecutor#execute(java.lang.Runnable, long, java.util.concurrent.TimeUnit)

    看一下该方法上的注释:

    dae5ec710ed931e0334265d4ca8d9cb2.png

    如果队列满了,则会等待指定时间后再次放入队列。

    如果再次放入队列的时候还是满的,则抛出拒绝异常。

    这个逻辑就类似于你去上厕所,发现坑位全都被人占着。这个时候你的身体告诉你,你括弧肌最多还能在忍一分钟。

    于是,你掐着表在门口,深呼吸,闭眼冥想,等了一分钟。

    运气好的,再去一看:哎,有个空的坑位了,赶紧占着。

    运气不好,再去一看:哎,还是没有位置,怎么办呢?抛异常吧。具体怎么抛就不说了,自行想象。

    af509060de8a56d4da439eefb38dc4f7.png

    所以我们看看这个地方,Tomcat 的代码是怎么实现的:

    06981ee37aaef3940e5d95016b24e1c6.png

    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 参数。

    0f6c442c382aa06512074d90c9249d84.png

    前面说过:submittedCount 参数 = 队列中的任务个数 + 正在运行的任务数。

    所以,这里需要进行减一操作。

    拒绝策略就说完了。

    但是这个地方的源码是我带你找到的。如果你想自己找到应该怎么操作呢?

    你想啊,你是想测试拒绝策略。那只要触发其拒绝策略就行了。

    比如下面这样:

    45da9955b805ff1578f6c35ccdb62b46.png

    给一个只能容纳 450 个任务的线程池提交 500 个任务。

    然后就会抛出这个异常:

    ad7f9edc0daf4da0aceec3b01ba2ee97.png

    就会找到 Tomcat 线程池的 174 行:

    28f786857c7c4e30fd74a7e1f7b07023.png

    然后你打上一个断点,玩去吧。

    那我们刚刚说的,可以在门口等一分钟再进坑是怎么回事呢?

    我们把参数告诉线程池就可以了,比如下面这样:

    60dff80cdbedadf6d82df6cace5ec7e3.png

    然后再去运行,因为队列满了后,触发拒绝异常,然后等 3 秒再去提交任务。而我们提交的一个任务 2 秒就能被执行完。

    所以,这个场景下,所有的任务都会被正常执行。

    现在你知道为了把你给它的任务尽量快速、全部的执行完成,Tomcat有多努力了吗?

    1c7a5fbf5cd8c60b74b6ef0368d96d49.gif

    小彩蛋

    在看 Tomcat 自定义队列的时候我发现了作者这样的注释:

    93174c018ac374aa18a595e2a1aa0f06.png

    这个地方作用是把 forcedRemainingCapacity 参数设置为 0。

    这个参数是在什么时候设置的呢?

    就是下面这个关闭方法的时候:

    org.apache.tomcat.util.threads.ThreadPoolExecutor#contextStopping
    55fa20a9828f929f9b9be646851d7425.png

    可以看到,调用 setCorePoolSize 方法之前,作者直接把 forcedRemainingCapacity 参数设置为了 0。

    注释上面写的原因是JDK ThreadPoolExecutor.setCorePoolSize 方法会去检查 remainingCapacity 是否为 0。

    至于为什么会去做这样的检查,Tomcat 的作者两次表示:I don't see why。I did not understand why。

    so,他 fake 了 condition。

    13178f27f00cf3966eed35fd1bc8f025.png

    总之就是他说他也不明白为什么JDK 线程池 setCorePoolSize 方法调小核心线程池的时候要的限制队列剩余长度为 0 ,反正这样写就对了。

    别问,问就是规定。

    于是我去看了 JDK 线程池的 setCorePoolSize 方法,发现这个限制是在 jdk 1.6 里有,1.6 之后的版本对线程池进行了大规模的重构,取消了这个限制:

    76d7af24b97d9004ffa3be596cba3449.png

    那 Tomcat 直接设置为 0 会带来什么问题呢?

    正常的逻辑是队列剩余大小 = 队列长度 - 队列里排队的任务数。

    550897a9013ca6495afa756ef5b4a3f8.png

    而当你对其线程池(队列长度为300)进行监控的时候正常情况应该是这样:

    9b7e2c02960bc1a1aa9e535688dfda00.png

    但是当你调用 contextStopping 方法后可能会出现这样的问题:

    6159ee55c70118af56f0cf4d1c90147a.png

    很明显不符合上面的算法了。

    好了,如果你们以后需要对 Tomcat 的线程池进行监控,且 JDK 版本在 1.6版本以上。那你可以去掉这个限制,以免误报警。

    好了,恭喜你,朋友。又学到了一个基本用不上的知识点,奇怪的知识又增加了一点点。

    819932ebff2251ff61f5f08d282a0872.png

    Dubbo 线程池

    这里再扩展一个 Dubbo 的线程池实现。

    e6463b3f883f89f1bebf9cdf20118774.png
    org.apache.dubbo.common.threadpool.support.eager.EagerThreadPoolExecutor

    你可以看一下,思想还是这个思想:

    ed8a42a07ee684776355e2f4b9e63ade.png

    但是 execute 方法有点不一样:

    9f01953fc776caaca418355e9fbc990e.png

    从代码上看,这里放入失败之后又立马调了一次 offer 方法,且没有等待时间。

    也就是说两次 offer 的间隔是非常的短的。

    其实我不太明白为什么这样去写,可能是作者留着口子好扩展吧?

    因为如果这样写,为什么不直接调用这个方法呢?

    java.util.concurrent.LinkedBlockingQueue#offer(E)

    也是作者是想在极短的时间能赌一下吧?谁知道呢?

    然后可以发现该线程池在拒绝策略上也做了很大的文章:

    1c5bca27cf76d4ecd0848ad20db746eb.png

    可以看到日志打印的非常详尽,warn 级别:

    24c9790dc3bf33409e99880c74651a0f.png

    dumpJStack 方法,看名字也知道它是要去 Dump 线程了,保留现场:

    cf32d09b261537eb5d5cae578b115a3d.png

    在这个方法里面,他用了 JDK 默认的线程池,去异常 Dump 线程。

    等等,阿里开发规范不是说了不建议用默认线程池吗?

    其实这个规范看你怎么去拿捏。在这个场景下,用自带的线程池就能满足需求了。

    而且你看第二个红框:提交之后就执行了 shutdown 方法,上面还给了个贴心警告。

    必须要 shutdown 线程池,不然会导致 OOM。

    这就是细节呀,朋友们。魔鬼都在细节里!

    这里为什么用的 shutdown 不是 shutdownNow ?他们的区别是什么?为什么不调用 shutdown 方法会 OOM?

    知识点呀,朋友们,都是知识点啊!

    98f3a3080c50e56bcac5dedffc343c3a.png

    好了,到这里本文的分享也到了尾声。

    以后当面试官问你 JDK 线程池的运行流程的时候,你答完之后画风一转,再来一个:

    其实我们也可以先把最大线程数用完,然后再让任务进入队列。通过自定义队列,重写其 offer 方法就可以实现。目前我知道的 Tomcat 和 Dubbo 都提供了这样策略的线程池。

    轻描淡写之间又装了一个漂亮逼。让面试官能进入下一个知识点,让你能更多的展现自己。

    86af2bdd338b82acb72d7e96f907460c.png

    来源:https://mp.weixin.qq.com/s?__biz=MzIxNTQ4MzE1NA==&mid=2247485707&idx=1&sn=237055cf8fe1c2674040a5b1cd65f1f5&utm_source=tuicool&utm_medium=referral

    来源于why不止技术 ,作者why技术

    展开全文
  • 电脑上用的桌面便签小工具有很,比如系统便笺,再比如浏览器或是某些应用程序中的便签插件等,也有可以直接下载的便签小程序,甚至是一些便签小工具的网页版也能记便签。电脑上可以用的便签小工具有这么,哪一款...

    电脑上用的桌面便签小工具有很多比如系统便笺再比如浏览器或是某些应用程序中的便签插件等也有可以直接下载的便签小程序甚至是一些便签小工具的网页版也能记便签

    电脑上可以用的便签小工具有这么多哪一款比较好用值得推荐给大家呢电脑上有什么好用的第三方便签APP值得推荐吗

    这篇文章的主要内容小编就跟大家分享一款电脑上用的深受上班族喜爱的桌面小便签为大家介绍下商务办公云便签敬业签都有哪些实用的小功能

    25f3885028c7c65bb03d9a6fe4d958fe.png

    Windows电脑为例电脑设备上可以下载安装桌面版便签程序或是直接登录网页版使用,PC端和web端登录同一个便签账号和密码便签数据支持自动云同步对于安卓手机设备苹果手机设备等也能免费安装便签app与电脑版同步电脑手机多端云同步是这款桌面工作便签小工具的亮点功市面上很少有这样的多端自动免费实时云同步的云便签

    对于使用过电脑系统便笺小工具的网友一定深有体会电脑系统便笺无法添加时间提醒只具备简单的记事功能将需要备忘的事情记录到上面之后无法添加时间定时通知为此只得寻求可以时间提醒的第三方便签软件

    7d697f9db5aef5264c6858b98b6b80a4.png

    而在敬业云便签上记事和提醒都是基础免费功能将事情记录到便签中不仅可以添加指定时间提醒还能设置重复周期标注重要事件提醒等另外便签中的提醒到期后还能借助手机微信公众号企业微信群助手手机来电响铃手机短信消息通知等方式同步推送消息提醒多层次全方位地解决备忘事件的提醒问题

    想要了解更多关于商务云便签敬业签的功能教程与使用体验不如现在下载安装体验吧https://www.jingyeqian.com/xiazai/ 

    展开全文
  • 每天为你更新不同手工艺术,编织,刺绣,串珠,布艺,皮艺,剪纸,折纸,钩编精品,粘土软陶,国内外手工艺术等精彩文章,在这里愿与您共同分享,QQ号:88461358只知道家里没地种菜了就泡沫箱或各种花盆,但是...

    29a95872cdfb8c586c6944c96288f24f.gif

    每天为你更新不同的手工艺术,编织,刺绣,串珠,布艺,皮艺,剪纸,折纸,钩编精品,粘土软陶,国内外手工艺术等精彩文章,在这里愿与您共同分享,QQ号:88461358

    fd13b731eec3e0337b73ab5a3afa47ae.png

    只知道家里没地种菜了就用泡沫箱或各种的花盆,但是鸡蛋托做菜你试过吗?不需要太多的土,只要能覆盖鸡蛋托的高度,就能把菜种好,那些长得快又高的菜苗,特别适合种在鸡蛋托上,除了能保水保湿,还可以锁住土里的营养成份。每棵苗都能有规律地形成一个属于自己的空间,不会轻松被抢走多余的养份,省时也省力,不会种菜的也可以试试。

    e47142007df0cdea9e5b36dbff5ee372.png

    用蛋托来试试种植小萝卜,准备木条,钉一个架子,家里有阳台的也可以试一下,或有院子,地不大, 也可以这样分层管理,上面种菜,下面可以放杂物。或直接做一个浅盘状的大花盆,能放置鸡蛋托的就可以。

    deadf143430a7be838e202525eac322e.png

    cb6c53e67d4c6096e9274f607157689a.png

    3469cbc4e14b8216e265d64f481d9e3c.png

    8278438bc5215514ac5ba3381ccc69d1.png

    上面再用木条围边,用于放置鸡蛋托。

    09f60fa4d2879d679e96eb12d7d72aa2.png

    架子完成,先在上面铺上一层防水布。

    c0a542242084331a3dcde32a104f3ddd.png

    之后覆土,撒上蛋壳粉做底肥,也可以直接买底肥代替。

    61681bc679ada3e16f84c754ed2e0979.png

    这里插播一下蛋壳粉的做法,蛋壳洗干净,倒扣晾干。

    cc66fcc8d012759b5980184a852f2e01.png

    之后在蛋壳上用竹签或筷子戳个洞,用绳子一个个串起来晾晒。

    30f509d2682acc0bc1eaab934fab749f.png

    晾晒完后,用粉碎机或破壁机打成粉,就是蛋壳粉了,以后种菜种花种菜都可以用。

    b08a92c623b39cac944c74a96057712a.png

    也可以不打成粉,把蛋壳捣碎撒在土的表面当肥料。

    0761799ed9b3a094793cef29e6bd7fe7.png

    打成粉吸收会更快一点。

    f8c93e54a43e2aac93ac01697635eca1.png

    回到正题,蛋壳粉撒好后用铲子翻拌均匀。

    7651a54c031fc3cb14ef09fc257ad7e6.png

    铺上蛋托

    45f6ae10725a3ebc8a51fb50e8ed1329.png

    f96cbec00cd84d581afcf93229a79f28.png

    覆土

    96fa083bf7580480e22ac75c77bf6934.png

    浇水

    48f88ae6b1c532bd4112c743ec51a135.png

    4a36b4afbfedd2cc88122c16a8e8bd04.png

    用筷子或竹签在蛋托的每个洞上戳个小孔。之后在小孔上放进几颗小萝卜的种子。

    1992283106fac111cfcafab929abc5cf.png

    把小孔覆盖好

    7ac16d3a16690227aeba79e691172905.png

    浇水

    34bd9a8ed4c7dbd1f30cf0ad3b05df99.png

    16天后,就长出新芽了。

    25ad13bc9b3e2fdf82c337d0f5827f3d.png

    之后筛选掉长得特别小的苗

    430c89bbef14e05a39ef18625471ec49.png

    日常养护,等30天后,萝卜就能收成了。

    30bd0c38bd388dbe8907a5751cae7e93.png

    翠绿的萝卜苗剪下来可以用盐腌成酸菜,或用水焯一下切粒,加姜蒜炒成一盘美味的菜。切下来的小萝卜,就能发挥你的厨艺做出百种吃法。

    e28cef375e5d00f7f8f3663e902c346c.png

    77940489da45d5f3f249e5efe78250b9.png

    戳视频查看详细教程

    1350e04263f863d31f4673d06c2fcf54.png

    展开全文
  • 说点不一样线程池执行策略和线程拒绝策略,探讨怎么让线程池先完最大线程池再把任务放到队列中。荒腔走板大家好,我是 why,一个四川程序猿,成都好男人。先是本号特色,技术分享之前先简短荒腔走板聊聊生活...
  • 对于一个最简单Web项目架构来说,Tomcat是前军,SSM是中军,Mysql是后方,而我们通常非常重视SSM框架学习,而忽略Tomcat,如果能够懂得关于Tomcat的底层原理知识,那么对于日常开发工作或项目性能调优是非常...
  • Java编写Servlet、jsp程序需要部署在Web容器上,才能实现功能。Tomcat体系结构一、Connector和Container1.Tomcat有两个功能:●和客户端浏览器进行交互,进行socket通信,将字节流和Reques...
  • 不止有PS,每个人爱好和需求都不一样,现在不管是做图工具还是软件都有很。像图怪兽、稿定设计、创客贴这几个都是不错做图工具,适合不懂PS小白使用。下面就讲一下它们介绍与用途吧。一、图怪兽是一个在线...
  • <br />问题补充:</strong><br />还有一点就是Apache+Tomcat做LB是可以,但是我对于这种群集感觉是存在隐患,有个问题不太好解决,如果tomcat下要生成独立文件,那么如何使tomcat实现资源同步?
  • 前提:  记录这个文章是因为... 运动个web 项目,设置不同端口,需要tomcat吗 ,答案是不需要eclips开发同学,可以下面操作,同时启来个web服务,以tomcat8为列 示例: 1.eclips配置tom...
  • tomcat多版本

    2010-10-29 16:54:00
    Tomcat 版本更新很快,各个版本也不尽相同,如果你现在使用... 要想让不同版本 Tomcat 并存於你电脑上,第一个要求是,千万不要下载 tomcatxxx.exe 安装程式,也就是说,Tomcat 不要安装,请
  • <p><br>8080端口的Tomcat用的Tomcat/6.0.20版本,另一个端口用的Tomcat/6.0.14版本,报的漏洞比这个还些。 (;">我怀疑这个评估就是获取Tomcat版本后查询此版本已报出的漏洞给出的) <br>针对每个漏洞,给...
  • 一个激进创建线程弹性线程池更符合我们需求,你能给出相关实现吗?实现后再测试一下,是否所有任务都可以正常处理完成呢? 既然选择先扩容线程池再加入队列,那为什么不干脆...不是也提倡不同任务不同
  • 我看到网上很文章都说。tomcat5是支持EL语言。但是为什么我做jsp页面,${}输出不了东西呢? 比如我这样 ${s} 但是为什么这样我是输出不了呢?...
  • 因为一些原因,我们现在4个tomcat分别部署了4个不同工程,现在我们需要使用IIS来转发请求,来屏蔽掉访问端口。 请问下大家 我因该怎么做呢? 或者说有什么好建议吗? 谢谢大家拉~
  • 进入供暖季,家家户户都上了地暖和暖气片,外面虽然很冷,但屋子里面还是洋洋,别提舒服了。虽然屋里暖气很舒服,但为了身体健康,还是要经常打开窗户进行换气。然而最近热力公司却发布了这样一条公告,大概...
  • 做开发都知道平时开发一个tomcat足够啦,但是如果老板让你做一个web service 接口,这是你需要两个tomcat分辨启动进行调用,当然你也可以在一个tomcat下进行启动调试,但不如两个tomcat进行调试比较有正规感吗。...
  • tomcat发布个项目,请问有没有办法让tomcat调用一个host的配置,就是保持这tomcat的域名配置是一样的,请指点,谢谢 <strong>问题补充:</strong><br />我用的所有请求交给tomcat来处理。这样只需要...
  • 试了网上很方法,还是出现这个问题,tomcat5-8都是这个问题,jdk5-7都换过了还是这个样子,有时间帮忙看看呗 严重: Exception sending context initialized event to listener instance of class org....
  • 我们知道Tomcat的架构设计是清晰、模块化,其拥有很组件,假如我们要启动Tomcat,可以一个一个启动组件,但这样启动有很缺点,不仅麻烦,而且容易漏了组件启动,还会对后面动态组件扩展带来麻烦。...
  • 在一个配电箱内,根据不同电器有需要漏电开关控制,有不一定要漏电开关空气开关控制即可,因此两种开关同时装或叫混合装比较常见。或者总开关与分路开关之间,前者空气开关后者漏电开关,反过来前者...
  • 我的hibernate的dao之类的用的是myeclipse默认生成的,里面有好多log4j的日志代码,和这个有关吗?而且那个默认生成的baseHibernateDao里面有关session的代码是不是有问题?老师给我改成单例模式才好用。开始的时候...
  • ,没错,但不知道你是 否将N个应用都一个Tomcat实例来服务呢?这样弊端是明显,某个应用出现问题,需要重新启动Tomcat服务,那势必会影响其他正常运行N- 1个服务,这样效果显然不尽人意。理想情况是...
  • nginx来做转发对外都是80端口里面可以装tomcat如果不用nginx安装tomcat就要指定端口 那多个tomcat都是8080端口吗? 这个不可能啊,端口只能被一个应用程勋使用,启动会报端口被占用 哦,我明白了,每个...
  • 得到Tomcat连接池当前连接数

    千次阅读 2013-09-30 11:20:07
    关于如何配置Tomcat数据库连接池,网上已有太文章了。可是找不到一篇文章能 告诉我,怎么能得到Tomcat... 先是看了Tomcat的相关源码,得到它了jakarta commons-dbcp这个包,又下载了 commons-dbcp这个包源吗
  • 关于如何配置Tomcat数据库连接池,网上已有太文章了。可是找不到一篇文章能... 先是看了Tomcat的相关源码,得到它了jakarta commons-dbcp这个包,又下载了commons-dbcp这个包源吗。终于在BasicDataSource找...
  • 现在我不明白的是这个war文件是怎样传上来的,因为系统管理员把8080端品没关掉,我们是用的8009和apache配合就可以的,里面有admin和manager这两个目录没有去掉,但奇怪的是我记得以前我默认的tomcat里面的tomcat-users....
  • Tomcat下面有个项目,内存不够,jar包基本都是一样,那么想说共用jar包 ,在Tomcat的catalina下面也设置了shared.loader=${catalina.base}/shared/lib,${catalina.base}/shared/lib/*.jar。maven打包项目...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 172
精华内容 68
关键字:

tomcat用的多吗