创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
线程池的意义:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用可以执行多个任务
2.可以根据系统的承受能力调整线程池中工作线程的数目,防止因为消耗过多的内存把服务器累趴下
3.线程池的作用就是限制系统中执行线程数量根据系统的环境情况可以自动或手动设置线程数量,达到运行的最佳效果
构造函数参数解释:
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;
}
corePoolSize 线程池中要保留的线程数,即使他们都空闲,除非设置了allowCoreThreadTimeOut才会处理他们
maximumPoolSize 线程池中允许的最大线程数
keepAliveTime 当线程数大于内核数时,这是多余的空闲线程将在终止之前等待新任务的最长时间。
unit keepAliveTime的时间单位
workQueue 在执行任务之前用于保留任务的队列
threadFactory 执行程序创建新线程时要使用的工厂
handler 由于达到线程边界和队列容量而在执行被阻止时使用的处理程序
ThreadPoolExecutor的运行机制:
当提交新任务,并且正在运行的线程少于corePoolSize线程时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理请求。线程数量达到了corePoolSize则将任务移入队列等待。如果运行的线程数大于corePoolSize但小于maximumPoolSize,则仅在队列已满时才创建新线程。队列满了,线程数量又达到了maximumPoolSize则拒绝任务
通过将corePoolSize和maximumPoolSize设置为相同,可以创建固定大小的线程池。通过将maximumPoolSize设置为本质上不受限制的值,例如Integer.MAX_VALUE,可以允许池容纳任意数量的并发任务。通常,核心线程数和最大池大小仅在构造时设置,但也可以使用setCorePoolSize和setMaximumPoolSize动态更改
ThreadPoolExecutor使用ThreadFactory创建新线程,如果没指定ThreadFactory则使用defaultThreadFactory。
使用defaultThreadFactory创建的线程,都在一个ThreadGroup里面,有着相同的优先级(NORM_PRIORITY),都是非守护线程。
通过提供其他ThreadFactory,可以更改线程的名称,线程组,优先级,守护程序状态等。
任务队列
a.synchronousQueue:这个队列接收到任务时,会直接提交给线程处理而不保留他,如果所有的线程都在工作怎么办?那就新建一个线程处理这个任务,所以为了保证不出现线程数达到maximumPoolSize而不能新建线程的错误使用这个类型的队列的时候maximumPoolSize一般指定成无限大。
b.LinkedBlockingQueue 这个队列接收到任务的时候如果当前线程数小于核心线程数,则新建线程(核心线程)处理这个任务,如果当前线程数等于核心线程数则进入队列等待,由于这个队列没有最大值限制,即所有超过核心线程数的任务都被添加到队列中。
c.ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候如果没有达到corePoolSize的值,则新建核心线程执行任务,如果达到了则入队等候,如果队列已满则新建线程(非核心线程)执行任务,又如果总线程数达到了maximumPoolSize并且队列也满了,则拒绝。
d.DelayQueue 队列内的元素必须实现Delayed接口这个队列接收到任务时,首先入队,只有到达了指定的延时时间才会执行任务
拒绝策略
当线程池已经关闭,或者到达了最大线程数(maximum)和任务队列已经满了的时候,会引发拒绝策略。 execute方法会调用 RejectedExecutionHandler 的 rejectedExecution (Runnable, ThreadPoolExecutor) 方法。
默认情况下,使用ThreadPoolExecutor.AbortPolicy,处理程序在拒绝时抛出RejectedExecutionException
ThreadPoolExecutor.CallerRunsPolicy 调用者所在的线程执行任务(会减慢新任务的提交速度)
ThreadPoolExecutor.DiscardPolicy 无法执行的任务将会被简单的删除
ThreadPoolExecutor.DiscardOldestPolicy 如果执行程序未关闭,则丢弃工作队列开头的任务,然后重试执行(这可能再次失败,从而导致重复执行此操作)
线程池的回收
如果程序中不再引用没有剩余线程的线程池,线程池将自动关闭。即使在用户忘记调用 shutdown() 的情况下也要确保收回未引用的池。
必须使用allowCoreThreadTimeOut方法和keepAliveTime设置合理的保活时间以使未使用的线程最终死掉。
注意
创建线程池禁止使用Executors工厂方法创建
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是底层工作队列使用LinkedBlockingQueue,并且没指定容量,默认容量是Integer.MAX_VALUE。堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
网友评论