在使用java线程池时,我们都习惯于使用Executors来创建线程池,常见的线程池有:
- newFixedThreadPool
- newSingleThreadExecutor
- newCachedThreadPool
而这些线程池其实都是使用ThreadPoolExecutor来创建的,其主要构造参数:
- corePoolSize
核心线程数 - maximumPoolSize
最大线程数 - keepAliveTime
线程最大闲置时间 - workQueue
存放等待运行任务的队列
任务提交时线程是的运行机制:
当向线程池提交任务时,首先看线程池是否有空闲线程,如果有就将任务分配到一个空闲线程中运行。
而如果没有,则要看当前正在运行的线程数大小情况,如果当前正在运行的线程数小于corePoolSize,那么就直接创建一个新线程,然后将任务分配给此线程运行。
而如果当前线程数已达到corePoolSize,那么则需要将此任务存放到workQueue里,等待空闲线程来运行此任务。
而如果workQueue队列也满了,那么则又需要查看当前线程数是否小于maximumPoolSize,如果小于maximumPoolSize,那么就直接创建一个新线程去运行此任务,否则此时线程数已到达maximumPoolSize,并且workQueue队列也满了,就会报RejectedExecutionExecption错误。
当线程池中的很多空闲的线程,并且总的线程数超过了corePoolSize时,则如果某个线程空闲的时间超过了keepAliveTime,则会此线程终止。
理解了ThreadPoolExecutor的各个参数后,现在回头再分析下Executors创建的各个线程池的特点:
- newFixedThreadPool
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
corePoolSize和maximumPoolSize为用户指定的值nThreads,意味着线程中的最大线程数为用户指定的nThreads。workQueue为LinkedBlockingQueue的无界队列,即没有大小限制。所以,此线程池中的线程数最多为一个,但是可以无限向线程池中提交任务而不报错,只是这些任务会被存放在workQueue中等待运行,这样很有可能造成workQueue的元素不断增多,导致OutOfMemeory。
- newSingleThreadExecutor
new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
corePoolSize和maximumPoolSize为1,workQueue为LinkedBlockingQueue的无界队列,即没有大小限制。这么看来newSingleThreadExecutor只是newFixedThreadPool的一个特列,只是最大线程数为1而已,它同样也会有workQueue无限大,导致的OutOfMemeory问题。
- newCachedThreadPool
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
corePoolSize为0,maximumPoolSize为整型的最大值,这意味线程池中可以最多创建整型的最大值数量的线程,某种程度上可以视为没有数量限制。workQueue为SynchronousQueue队列,此队列可以想象成长度为0的队列,这意味着当线程池中的线程不够时,任务不会存在workQueue队列中,等待空闲的线程来运行它,而是直接要么报RejectedExecutionException,要么创建新的线程来运行它,而maximumPoolSize很大,几乎不会限制创建线程的数量,因而会直接创建线程来运行它。在大量任务并发提交时,很有可能造成线程池中创建的线程数量过多,从而导致OutOfMemory,因为创建线程需要堆栈内存。
所以,在使用Executors来创建线程池时,我们一定要弄清楚所创建的线程池的特点,否则可能会出现很多意想不到的问题。只有弄清楚各个线程池的特点,我们才能根据各个场景更好的选择使用哪个线程池,从而规避问题。
其实在使用线程池时,我们最好尝试使用ThreadPoolExecutor来创建,一方面我们可以控制线程池的各个参数,另一方面也便于我们更好的理解线程池的运行机制。
Executors中还有其他类型的一些线程池,但都逃不了ThreadPoolExecutor,可以试着使用上面的方法去分析下这些线程池的特点。