在程序员的日常工作中,免不了使用多线程,那么就离不开线程池,大家都是怎么使用线程池的呢?
小菜(本人)刚开始是使用java中自带的java.util.concurrent.Executors类,通过Executors.newFixedThreadPool(5)
方法创建线程池,并无不妥;直到使用了某开发手册后,竟然提醒
**FixedThreadPool中允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM**
,于是决定一探究竟.
线程池的核心参数
通过java.util.concurrent.ThreadPoolExecutor类,找到其构造函数,如下:
/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
发现有7个必传参数,分别是:
corePoolSize: 核心线程数,开始就创建好的,可以随时执行任务,要求是必须大于0
maximumPoolSize: 最大线程数,要求是大于等于核心线程数
keepAliveTime: 非核心线程的空闲存活时间,要求是必须大于0
unit: 时间单位,与keepAliveTime配合使用
workQueue :当线程池满了,任务就会放入队列中等待被执行
threadFactory: 线程工厂,可以决定线程的名字,属性等
handler: 拒绝处理程序策略,workQueue 被填满,继续接收任务时,会抛出异常
为什么可能会oom
有了以上知识基础,就可以进行下一步了,进入Executors.newFixedThreadPool(5)方法内部
/** * Creates a thread pool that reuses a fixed number of threads * operating off a shared unbounded queue. At any point, at most * {@code nThreads} threads will be active processing tasks. * If additional tasks are submitted when all threads are active, * they will wait in the queue until a thread is available. * If any thread terminates due to a failure during execution * prior to shutdown, a new one will take its place if needed to * execute subsequent tasks. The threads in the pool will exist * until it is explicitly {@link ExecutorService#shutdown shutdown}. * * @param nThreads the number of threads in the pool * @return the newly created thread pool * @throws IllegalArgumentException if {@code nThreads <= 0} */ public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
可以看到LinkedBlockingQueue队列没有指定大小,那么就会使用默认值,默认值是多少呢?我们点进去看一下
/** * Creates a {@code LinkedBlockingQueue} with a capacity of * {@link Integer#MAX_VALUE}. */ public LinkedBlockingQueue() { this(Integer.MAX_VALUE); }
Integer的最大值,也就是说线程池中可以不停的接收任务,可能内存已经放不下,队列还没满,这时就会出现oom了.
以下是一个正例:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 3, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(5) );
引出下一个问题
当maximumPoolSized大于corePoolSize ,什么时候会创建新线程
上面的例子中:核心线程数是5,最大线程数是10,空闲非核心线程的存活时间为3分钟,可等待执行的任务数是5,线
程工厂和拒绝策略暂不考虑,使用默认的即可.很多人想当然的认为,当线程池中的任务大于5时,就会创建新的线程执
行任务,直到达到10时,任务才会被放入队列等待.其实不是的,真实情况如下图所示:
-
当线程池中的缓存队列满时,才会创建新的线程执行任务,否则会一直等待核心线程的释放.
-
当非核心线程达到最大值且队列满时,有新的任务提交,就会被拒绝,并抛出RejectedExecutionException.
有兴趣的同学可以思考下,为什么不是先创建新线程执行,后放入队列等待.
在文章的结尾,总结下核心的两个知识点:
-
尽量手动创建线程池,了解必要的参数,避免安全隐患.
-
核心线程用尽时,线程池的执行策略是先放入队列等待,然后根据情况判断是否创建新线程.