线程池的作用###
1、减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务;
2、可以根据系统的承受能力,调整线程池中工作线程的数据,防止因为消耗过多的内存导致服务器奔溃。
使用线程池,要根据系统的环境情况,手动或自动设置线程数目。少了系统运行效率不高,多了系统拥挤、占用内存多。用线程池控制数量,其他线程排队等候。一个任务执行完毕,再从队列中取最前面的任务开始执行。若任务重没有等待任务,线程池浙一资源处于等待。当一个新任务需要运行,如果线程池中有等待的工作线程,就可以开始运行,否则进入等待队列。
线程池结果图###
ThreadPoolExecutor###
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler)
ThreadPoolExecutor类的另外三个构造器都是调用上面这个构造器进行初始化工作的。
int corePoolSize:核心池的大小。创建线程池后,默认情况下,线程池中没有任何线程,而是等待有任务到来才创建线程去执行任务。默认情况下,在创建线程池后,线程池钟的线程数为0,当有任务到来后就会创建一个线程去执行任务。
maximumPoolSize:池中允许的最大线程数,这个参数表示了线程池中最多能创建的线程数量,当任务数量比corePoolSize大时,任务添加到workQueue,当workQueue满了,将继续创建线程以处理任务,maxmumPoolSize表示的就是workQueue满了,线程池中最多可以创建的线程数量。
keepAliveTime:表示线程没有任务执行时最多保持多久时间终止。默认情况下,只有当线程池中的数量大于corePoolSize时,才会起作用,直到线程池中的线程数不大于corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。
unit:参数keepAliveTime的时间单位,有七种取值:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLSECONDS; //毫秒
TimeOut.MICROSECONDS; //微秒
TimeOut.NANOSECONDS; //纳秒
workQueue:一个阻塞队列,用来存储等待执行的任务。一般来说,这里的阻塞队列有一下几个选择:
ArrayBlockingQueue; //基于数组的先进先出队列,此队列创建时必须指定大小
LinkedBlockingQueue; //基于链表的先进先出队列,如果没有指定队列大小,默认Integer.MAX_VALUE
SynchronousQueue; //不会保存提交的任务,直接创建一个线程来执行新来的任务
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关
ThreadFactory:线程工厂,主要用来创建线程。
handler:当拒绝处理任务时的策略,有四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认这种拒绝策略)。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
关于corePoolSize与maximumPoolSize举例理解###
1、线程池数小于corePoolSize,新任务不排队直接添加新线程;
2、线程池数大于等于corePoolSize,workQueue未满,将新任务加入workQueue等待,而不是添加新线程;
3、线程池数大于等于corePoolSize,workQueue已满,线程数小于maximumPoolSize,添加新的线程来处理被添加的任务;
4、线程池数大于corePoolSize,workQueue已满,并且线程数大于等于maximumPoolSize,新任务被拒绝,使用handler处理被拒绝的任务。
注:建议使用较为方便的Executors工厂方法创建线程池:
Executors.newCachedThreadPool() : //无界线程池,可以进行线程自动回收;
Executors.newFixedThreadPool(int) : //固定大小线程池
Executors.newSingleThreadExecutor() : //单个后台线程
//上面三种方法都使用了场景预定义设置
Executor###
线程池的重点不是类怎么用,而是在合适的场景下使用合适的线程池,所谓合适的线程池就是ThreadPoolExecutor的构造方法传入不同的参数,构造出不同的线程池,以满足实际需求。
单线程线程池#####
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
单线程线程池运行的线程数是1,选择无界的LinkedBlockingQueue。不管传入多少任务都排序,前面一个任务执行完毕,再执行队列中的线程。这里第二个参数maximumPoolSize是没有意义的,因为maximumPoolSize描述的排队的任务多过workQueue的容量,线程池中对多只能容纳maximumPoolSize个任务,现在workQueue是无界的,也就是说排队的任务永远不会多过workQueue的容量,maximumPoolSize设置成多少都没有意义。
固定大小线程池#####
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
固定大小的线程池和单线程的线程池差不多,无非是让线程池中能运行的线程手动制定了nThread。选择的LinkedBlockingQueue,所以maximumPoolSize是无意义的。
无界线程池#####
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
无界线程池,就是不管多少任务提交进行,都直接运行。无界线程池采用SynchronousQueue,采用这个线程池就没有workQueue容量一说了,只要添加进去的线程就会被拿去用。无界线程池的线程数是没有上限的,所以以maximumPoolSize为主了,设置为一个近似无限大的Integer.MAX_VALUE。另,单线程线程池和固定大小线程池线程是不会自动回收的,也即是说保证提交进来的任务最后会被处理,但不保证什么时候处理。无界线程池是设置了回收时间的,由于corePoolSize为0,所以只要60秒没有被用到的线程都会被直接移除。
workQueue#####
workQueue就是排队策略。排队策略描述的是当前线程大于corePoolSize时,线程以什么样的方式等待被运行。
排队有三种策略:直接提交、有界队列、无界队列。JDK使用了无界队列LinkedBlockingQueue作为workQueue而不是有界队列ArrayBlockingQueue,尽管后者可以对资源进行控制,但是有界队列相比无界队列有三个缺点:
1、使用有界队列,corePoolSize、maximumPoolSize两个参数势必要根据实际场景不断调整以求达到一个最佳,这会给开发带来极大的麻烦,必须经过大量的性能测试。所以干脆用无界,永远添加到队列中,不会溢出,自然maximumPoolSize就没啥用了,只需要根据系统处理能出调整corePoolSize就可以;
2、放置业务突刺,尤其是在Web应用中,某些时候突然大量请求的到来。使用无界队列,不管什么时候,至少保证所有任务能够被处理,有界的超过maximumPoolSize的任务就可能被丢弃掉。
3、有界队列大小和maximumPoolSize也需要相互折中,这又是一个难搞的地方。
线程池容量的动态调整#####
setCorePoolSize:设置核心池大小
setMaximumPoolSize:设置线程池最大能创建的线程数目大小
线程池的关闭#####
shutdown(): 不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但不再接受新任务
shutdownNow(): 立即终止线程池,并尝试打算正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务