一个简单的定时任务小例子
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资
源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者
“过度切换”的问题。
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool :
允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
2) CachedThreadPool 和 ScheduledThreadPool :
允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。
以上是来自灵狐插件的提示
newScheduledThreadPool、newCachedThreadPool 的 maximumPoolSize 都是 Integer.MAX_VALUE,
如果有一些线程异常等待,可能会导致创建大量线程
而 newSingleThreadExecutor 和 newFixedThreadPool 都是LindedBlockingQueue,其长度无限制,可能会导致等待队列无限长
所以我们使用 ThreadPoolExecutor
corePoolSize:
线程池启动后,在池中保持的线程的最小数量。需要说明的是线程数量是逐步到达corePoolSize值的。例如corePoolSize被设置为10,而任务数量只有5,则线程池中最多会启动5个线程,而不是一次性地启动10个线程。
maxinumPoolSize:
线程池中能容纳的最大线程数量,如果超出,则使用RejectedExecutionHandler拒绝策略处理。
keepAliveTime:
线程的最大生命周期。这里的生命周期有两个约束条件:一:该参数针对的是超过corePoolSize数量的线程;二:处于非运行状态的线程。举个例子:如果corePoolSize(最小线程数)为10,maxinumPoolSize(最大线程数)为20,而此时线程池中有15个线程在运行,过了一段时间后,其中有3个线程处于等待状态的时间超过keepAliveTime指定的时间,则结束这3个线程,此时线程池中则还有12个线程正在运行。
unit:
这是keepAliveTime的时间单位,可以是纳秒,毫秒,秒,分钟等。
workQueue:
任务队列。当线程池中的线程都处于运行状态,而此时任务数量继续增加,则需要一个容器来容纳这些任务,这就是任务队列。这个任务队列是一个阻塞式的单端队列。
- ArrayBlockingQueue:先进先出队列,创建时指定大小, 有界;
- LinkedBlockingQueue:使用链表实现的先进先出队列,默认大小为Integer.MAX_VALUE;
- SynchronousQueue:不保存提交的任务, 数据也不会缓存到队列中, 用于生产者和消费者互等对方, 一起离开.
- PriorityBlockingQueue: 支持优先级的队列
threadFactory:
定义如何启动一个线程,可以设置线程的名称,并且可以确定是否是后台线程等。
handler:
拒绝任务处理器。由于超出线程数量和队列容量而对继续增加的任务进行处理的程序。
OK,ThreadPoolExecutor中的主要参数介绍完了。我们再说一下线程的管理过程:首先创建一个线程池,然后根据任务的数量逐步将线程增大到corePoolSize,如果此时仍有任务增加,则放置到workQueue中,直到workQueue爆满为止,然后继续增加池中的线程数量(增强处理能力),最终达到maxinumPoolSize。那如果此时还有任务要增加进来呢?这就需要handler来处理了,或者丢弃新任务,或者拒绝新任务,或者挤占已有的任务。在任务队列和线程池都饱和的情况下,一旦有线程处于等待(任务处理完毕,没有新任务)状态的时间超过keepAliveTime,则该线程终止,也就是说池中的线程数量会逐渐降低,直至为corePoolSize数量为止。
public static ExecutorService getExecutorService() { if (executorService == null) { synchronized (AHWMThreadFactory.class) { if (executorService == null) { executorService = new ThreadPoolExecutor(10, 100, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1024), // 设置队列大小 new ThreadFactoryBuilder().setNameFormat("MY-THREAD-POOL").build()); } } } return executorService; }
坑1:
如果corePoolSize 设置为0,和 corePoolSize 为1效果是一样的,无论maxinumPoolSize 是多少,这时候都是单线程在运行,适用于 单线程-异步执行的场景
坑2:
如果使用 new SynchronousQueue<Runnable>(),该队列不能自定义长度,maxinumPoolSize 的值设置一定要合理,比如 需要100个线程,maxinumPoolSize 设置为50,这时候 会抛出一个RejectedExecutionException异常,并且直接丢弃掉50个任务;使用 LinkedBlockingDeque,如果其长度 + maxinumPoolSize 小于 所需要的线程数,也是会抛出RejectedExecutionException。
关于 maxinumPoolSize corePoolSize 和 队列长度:
-
从线程池中获取可用线程执行任务,如果没有可用线程则使用ThreadFactory创建新的线程,直到线程数达到corePoolSize限制
-
线程池线程数达到corePoolSize以后,新的任务将被放入队列,直到队列不能再容纳更多的任务
-
当队列不能再容纳更多的任务以后,会创建新的线程,直到线程数达到maxinumPoolSize限制
-
线程数达到maxinumPoolSize限制以后新任务会被拒绝执行,调用 RejectedExecutionHandler 进行处理