线程池
通过new Thread()方法创建线程弊端比较多,通常是合理的使用线程池来创建线程。使用线程池有点如下:
降低系统资源小号,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等情况;
线程池提供了定时,定期以及可控线程数等功能的线程池
四种线程池
java通过Executors提供了四种线程池
newCachedThreadPoll
创建一个可缓存的无界线程池,如果线程池长度超过处理需要,可灵活回收空线程,若无可回收,则新建线程。当线程池中线程空闲空闲时间超过60s,则会自动回收该线程,当任务超过线程池的线程数,则创建新的线程,线程池的大小上线为Integer.MAX_VALUE,可以看作无限大。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}newFixedThreadPool
创建一个指定大小的线程池,可控制线程的最大并发数,超出的线程会在LinkedBlockingQueue阻塞队列中等待
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}newScheduledThreadPool
创建一个定长的线程池,可以指定线程池核心线程数,支持定时及周期性任务的执行
newSingleThreadExecutor
创建一个单线程化的线程池,它只有一个线程,用仅有的一个线程来执行任务,保证所有的任务按照指定顺序(FIFO,LIFO,优先级)执行,所有的任务都保存在队列LinkedBlockingQueue中,等待唯一的单线程来执行任务
线程池原理
Executors提供了上面四个静态工厂方法,这些方法最终都是通过ThreadPollExecutors类来完成的,Executors类提供的便捷的工厂方法能完成绝大多数用户场景。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}创建线程池,在构造一个新的线程池时,必须满足以下条件:
corePoolSize(核心线程数)必须大于或等于0
maximumPoolSize(最大线程数)必须大于或等于1
maximumPoolSize必须大于或等于corePoolSize
keepAliveTime(线程存活时间)必须大于或等于0
threadFactory(线程工厂)不能为空,默认为DefaultThreadFactory类
handler(拒绝策略)不能为空,默认策略为ThreadPoolExectuors.AbortPolicy
corePoolSize
核心线程数,当想线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建以一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize,才会根据是否存在空闲线程,来决定是否创建新的线程。除了利用提交新任务来创建和启动线程,也可以通过prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。
maximumPoolSize
最大线程数,线程池所允许的最大线程数,当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。对于无界队列,可以忽略该参数。
keepAliveTime
线程存活时间,默认情况下,当线程池的线程个数多于corePoolSize时,线程的空闲时间超过keepAliveTime则会终止。但只要keepAliveTime大于0,allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。另外,也可以使用setKeepAliveTime()动态地更改参数。
unit
存活时间单位
workQueue
任务队列,用于传输和保存等待执行任务的阻塞队列,可以使用此队列与线程池进行交互,如果运行的线程数小于corePoolSize,则Executor始终首选添加新的线程,而不进行排队;如果运行的线程数等于或多于corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程;如果无法将请求加入队列,则创建新的线程,除非创建此线程超出maximumPoolSize,在这种情况下,任务将被拒绝。
java为我们提供了7种阻塞队列的实现:
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,在未指明容量时,默认容量为Integer.MAX_VALUE
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现Comparable接口,也可以提供Comparator来对队列中的元素进行比较,跟时间没有任何关系,仅仅是按照优先级取任务
DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列,要求元素都实现Delayed接口,通过执行时延从队列中提取任务,时间没到任务取不出来
SynchronousQueue:一个不存储元素的阻塞队列,消费者线程调用take()方法的时候会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用put()方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回
LinkedBlockingQueue:使用双向队列实现的有界双端阻塞队列,双端意味着可以像普通队列一样FIFO,也可以向栈一样FILO
LinkedTransferQueue:它是ConcurrentLinkedQueue、LinkedBlockingQueue和SynchronousQueue的结合体,但是把它用在ThreadPoolExecutor中,和LinkedBlockingQueue行为一致,但是是无界的阻塞队列
threadFactory
线程工厂,用于创建新线程。由一个threadFactory创建的线程,属于同一个ThreadGroup,创建的线程优先级都为Thread.NORM_PRIORITY,以及是非守护进程状态。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n,其中m为线程池的编号,n为线程池内线程编号。
handler
拒绝策略,当线程池和队列都满了,表明线程池已达到饱和状态。
ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝,则直接抛出运行时异常 RejectedExecutionException。(默认策略)
ThreadPoolExecutor.CallerRunsPolicy:调用者所在线程来运行该任务,此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
ThreadPoolExecutor.DiscardPolicy:无法执行的任务将被删除。
ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重新尝试执行任务(如果再次失败,则重复此过程)。