线程池的概念
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
线程池的优势
(1) 降低系统资源消耗,通过重用现有的线程,降低创建和销毁线程的性能损耗。
(2) 方便管控线程并发数,如果线程数量无限制的增长,会导致内存占满而出现OOM。
(3) 提供了更加强大的功能。
线程池的主要参数
corePoolSize:核心线程数。当现有线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行任务,直到现有线程数大于或等于corePoolSize。
maximumPoolSize:线程池允许的最大线程数。
keepAliveTime:线程存活时间。当线程池现有线程数大于核心线程数,线程池会回收空闲线程,回收的规则是,线程的空闲时间如果超过keepAliveTime,该线程会被销毁,直到线程池现有线程数小于等于核心线程数。
timeUnit:时间单位。线程存活时间的时间单位。
workQueue:工作队列。用于保存等待执行任务的阻塞队列。
threadFactory:线程工厂。用于创建新线程,可以统一线程名和其他属性。
handler:线程拒绝策略。当线程池活跃线程数等于最大线程数并且阻塞队列已满,再加入的线程会执行此策略。
下图就是threadFactory的一个例子:
线程池的拒绝策略
线程池可选的拒绝策略有4种
AbortPolicy:该策略会拒绝请求,并抛出RejectedExecutionException异常。
CallerRunsPolicy:直接运行该线程。
DiscardPolicy:直接忽略,什么都不做。
DiscardOldestPolicy:将队列中最老的线程删除,将线程加入队列。
线程池可选的阻塞队列
线程池可选的阻塞队列有
ArrayBlockingQueue:数组阻塞队列,有界队列,拥有FIFO的特点。
LinkedBlockingQueue:链接阻塞队列,无界队列,默认最大长度为Integer.MAX_VALUE,拥有FIFO的特点。
PriorityBlockingQueue:优先阻塞队列,有界队列,可以自定义排序方法。
DelayQueue:延迟队列,无界队列,限定一段时间后获取。
SynchronousQueue:只存储一个元素的队列,加入一个元素后put会进入阻塞状态,当消费者调用take才会被唤醒。
LinkedTransferQueue:链接传输队列,相对LinkedBlockingQueue增加了tryTransfer和transfer方法。允许被打断。
LinkedBlockingDeque:双向链接阻塞队列,无界队列,默认最大长度为Integer.MAX_VALUE,拥有FIFO的特点。
Java帮我们实现的几个线程池
FixedThreadPool:特点是核心线程数与最大线程数相同,队列是使用的LinkedBlockingQueue。
CachedThreadPool:特点是使用SynchronousQueue作为队列,只存储一个元素。
SingleThreadExecutor:特点是只有一个线程的线程池,创建线程池时使用了FinalizableDelegatedExecutorService,这个类多了一个finalize方法用于关闭线程池。
ScheduledThreadPool:自定义核心线程数的定时器线程池。
WorkStealingPool:工作窃取线程,默认线程数是CPU的核心数,工作方式是在CPU闲置时让CPU执行任务。
SingleThreadScheduledExecutor:只有一个线程的定时器线程池。
线程池为什么要使用阻塞队列?
阻塞队列可以使线程进入阻塞状态释放CPU资源,避免大量活跃线程排队造成资源浪费。阻塞队列可以自动阻塞和唤醒,不需要人为处理。
线程池为什么需要先加入队列再扩充活跃线程数?
首先从源码入手,源码在扩充活跃线程的时候,会加个锁,来保证线程安全。频繁的创建线程和销毁线程会降低线程池的效率,所以选择先加入阻塞队列再扩充线程。