在操作系统中,线程是一个非常重要的资源,频繁创建和销毁大量线程会大大降低系统性能。Java线程池原理类似于数据库连接池,目的就是帮助我们实现线程复用,减少频繁创建和销毁线程。
ThreadPoolExecutor
ThreadPoolExecutor是线程池的核心类。首先看一下如何创建一个ThreadPoolExecutor。下面是ThreadPoolExecutor常用的一个构造方法:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
参数介绍:
corePoolSize
核心线程数量,线程池刚创建时,线程数量为0,当每次执行execute添加新的任务时会在线程池创建一个新的线程,直到线程数量达到corePoolSize为止。
workQueue
阻塞队列,当线程池正在运行的线程数量已经达到corePoolSize,那么再通过execute添加新的任务则会被加到workQueue队列中,在队列中排队等待执行,而不会立即执行。
maximumPoolSize
最大线程数量,当workQueue队列已满,放不下新的任务,再通过execute添加新的任务则线程池会再创建新的线程,但不会超过maximumPoolSize,如果超过maximumPoolSize,那么会执行拒绝策略。
keepAliveTime和unit
当线程池中线程数量大于 corePoolSize,如果一个线程的空闲时间大于keepAliveTime,则该线程会被销毁。unit则是keepAliveTime的时间单位。
RejectedExecutionHandler
拒绝策略,如果线程数量超过maximumPoolSize,则会使用拒绝策略,包括下面几种:
- AbortPolicy: 抛出异常
- CallerRunsPolicy: 直接运行线程的 run 方法,在当前线程执行任务
- DiscardPolicy: 丢弃任务
- DiscardOldestPolicy: 丢弃队列中最老的任务
总结一下线程池添加任务的整个流程:
- 线程池刚刚创建是,线程数量为0;
- 执行execute添加新的任务时会在线程池创建一个新的线程;
- 当线程数量达到corePoolSize时,再添加新任务则会将任务放到workQueue队列;
- 当队列已满放不下新的任务,再添加新任务则会继续创建新线程,但线程数量不超过maximumPoolSize;
- 当线程数量达到maximumPoolSize时,再添加新任务则执行拒绝策略
Executors
Executors提供了一些创建线程池的工具方法。
Executors.newSingleThreadExecutor()
源码实现:
new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
corePoolSize和maximumPoolSize都为1,也就是创建了一个固定大小是1的线程池,workQueue是new LinkedBlockingQueue<Runnable>()
也就是队列的大小是Integer.MAX_VALUE
,可以认为是队列的大小不限制。
由此可以得出通过该方法创建的线程池,每次只能同时运行一个线程,当有多个任务同时提交时,那也要一个一个排队执行。
Executors.newFixedThreadPool(int nThreads)
源码实现:
new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
类似Executors.newSingleThreadExecutor()
也是创建了一个固定大小的线程池,但是可以指定同时运行的线程数量为nThreads。
Executors.newCachedThreadPool()
源码实现:
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())
corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE
可以视为无穷大,workQueue是一个SynchronousQueue。SynchronousQueue可以认为是一个长度限制为0的队列,也就是向这个队列添加任务会永远是已满的状态。
由此可以得出通过该方法创建的线程池并不限制线程数量,每次添加的任务都会直接执行而不会放入workQueue,它的主要提供的功能是线程复用,但不能控制线程数量。