在处理一些比较复杂或者费时的任务的时候,我们常常会选择多线程的方式去处理。那么怎么创建多个线程呢,当然不可能是一个一个创建的方式创建。这时候我们通常会使用线程池创建多个线程。
1.降低资源消耗。线程池可以重复利用已创建的线程。
2.提高响应速度。当任务到达的时候,可以不用创建,就能立即响应。
3.提高线程的可管性。可以统一的对线程池里的线程进行统一分配,调优和监控。
如上图,是当一个任务来到之后,线程池的执行流程。接下来我们看下线程池的实现原理,如下图。
如图,我们可以看出,当一个线程任务到达的时候,将会放在BlockingQueue队列里,然后判断核心线程池是否满了,如果 没满,就创建线程执行任务。如果满了,就在阻塞队列里等着,当轮到自己的时候利用已有的线程执行任务.如果阻塞阻塞队列也满了.判断线程池是否满了,如果没满,就创建线程执行任务.如果满了,就执行饱和策略.
饱和策略大致有四种,就是上图的箭头4指向的小框里面的四种.
我们可以通过ThreadPoolExecutor来创建一个线程池.
创建一个线程池大致需要以下几个参数(面试也会经常问)
1.corePoolSize:核心线程池数大小,在阻塞队列没满的情况下,基本都是创建和使用核心线程池的线程,完成相应的任务.
2.runnableTaskQueue(任务队列):用于保存等待执行任务的阻塞队列.之前我们有一个章节,专门介绍了阻塞队列,这里就不展开详细介绍.只介绍下有哪几种队列可以使用.
(1)ArrayBlockingQueue
(2)LinkedBlockingQueue
(3)SynchronousQueue
(4)PriorityBlockingQueue
3.maximumPoolSize:线程池的允许创建的最大线程数.
4.ThreadFactory: 用于设置创建线程的工厂.
5.RejectedExecutionHandler(饱和策略):当线程池和队列都满的时候,需要选择一种策略来处理新提交的任务.默认是AbortPolicy,一共有四种.
(1)AbortPolicy:直接抛出异常
(2)CallerRunsPolicy:只用调用者所在线程来运行任务.
(3)DiscardOldestPolicy:丢弃队列里最近一个任务,并执行当前任务.
(4)DiscardPolicy:不处理,丢弃掉
6.keepAliveTime:工作线程空闲后,保持存活的时间.
7.6的时间单位.
线程池提交任务有两个方法:execute()和submit()
execute用来提交不需要返回值的任务
submit方法用于提交需要返回值的任务.线程池会返回一个fature类型的对象,通过这个fature对象可以判断任务是否执行成功,并且可以通过get方法获取返回值
调用线程池的shutdown或shutdownNow方法来关闭线程池
shutdown首先将线程池的状态设置为STOP,然后尝试停止所有正在执行或暂停任务的线程,并返回等待执行任务的列表.
shutdownNow只是将线程池的状态设置为SHUIDOWN状态,然后中断所有没有正在执行任务的线程.
想要合理的使用线程池,就得从任务的特性分析.
任务性质:cpu密集型任务,io密集型任务和混合型任务
任务优先级:高,中,低
任务执行时间:长,中,短
任务依赖性:是否依赖其他资源
cpu密集型任务尽可能配置少的线程,如cpu数量加一个
io密集型任务尽量多配置线程,因为他的线程不一定是一直在执行任务
混合型任务:尽量将其拆分为cpu密集型任务和o密集型任务,然后按照这两种任务的处理方式处理.
优先级任务的话,不太好处理,因为操作系统不一定任java里的优先级.但是还是可以使用优先级对列处理,会比较好.
依赖型任务尽可能设置多的线程,例如数据库连接,可能需要等待返回结果,等待时间越长cpu空闲越长,所以线程数设置的越大越好.同时,最好使用有界对列,这样出现问题可以及时发现.
所有的监控,基本都是获取一些主要的参数,来实现监控的目的.线程池也不例外,线程池提供了下面几个参数
takeCount:线程池所需要执行的任务数量
completedTaskCount:线程池在运行过程中已完成的任务数量
largestPoolSize:线程池里曾经创建过的最大线程数量.
getPoolSize:线程池的线程数量.
getActiveCount:获取活动的线程数
希望通过今天的介绍,大家都可以对线程池有一个清晰的认知.往后在用到线程池的时候,都可以正确的使用线程池应对各种场景.