Java中的线程池是运用场景最多的并发框架,几乎所有需要并发执行的程序都可以使用线程池,合理的使用线程池可以降低资源消耗,提高响应速度,提高线程的可管理性。
1.线程池的实现原理
当提交一个新任务到线程池时,线程池的处理流程如下:
线程池判断核心线程是否都在执行任务,如果不是则创建核心线程去执行任务,如果核心线程都在执行任务,线程池会判断任务队列是否已满,如果任务队列没有满,则将新提交的任务存储在任务队列中,如果任务队列满了,线程池会判断线程池中的线程是否都处于工作状态,如果没有则会创建线程池来处理任务,如果满了,则会执行饱和策略。
2.线程池的使用
我们可以通过ThreadPoolExecutor来创建一个线程池:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {...}
创建一个线程池需要输入几个参数,如下:
corePoolSize:核心线程池数量,在创建线程池之后,线程池默认没有任何线程,只有当任务过来的时候才会创建线程去执行任务,当线程池中的线程数达到corePoolSize后,之后到来的任务会被放入任务队列中。
maximumPoolSize: 线程池允许创建的最大线程数,如果任务队列满了,并且已创建的线程数小于最大线程数,则线程池会创建新的工作线程去执行任务。
keepAliveTime:线程池中的工作线程空闲后,保持存活的时间,如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。
unit:用于指定keepAliveTime参数的时间单位,这是一个枚举,可选的单位有毫秒(MILLISECONDS),分钟(SECONDS) 等。
workQueue:用于保存等待执行的任务的任务队列,通过线程池的execute方法提交的Runnable对象这个参数中。可以选择以下几个阻塞队列:
1).ArrayBlockingQueue:是一个基于数组结构的阻塞队列,此队列按照先进先出的原则对数组进行排序,此队列创建时必须指定大小。
2).LinkedBlockingQueue:是一个基于链表结构的阻塞队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE。
3).SynchronousQueue:一个不存储元素的阻塞队列,而是将直接新建一个线程来执行新来的任务。
threadFactory:线程工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
handler:饱和策略,当队列和线程池都满了,说明线程池处于饱和状态,那么必须采用一种策略来处理新提交的任务,这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。线程池有如下4种处理策略:
1).AbortPolicy:直接抛出异常
2).CallerRunsPolicy:该策略不抛弃任何任务,也不会抛出异常,他不会在线程池中的某个线程中执行任务,而是在调用execute()方法的线程中执行该任务。
3).DiscardOldestPolicy:则会抛弃下一个将会执行的任务,然后尝试重新提交该任务。
4).DiscardPolicy:不处理,丢弃掉。
3.关闭线程池
停止线程池执行主要有两个方法,第一个是shutdown(),第二个是shutdownNow()方法,他们的主要区别是:
调用shutdown()方法,将会把线程池的状态标记为SHUTDOWN,线程池将不会接收任何新的任务,并且会继续执行队列里的任务,直到所有任务执行完成,终止线程池。
调用shutdownNow()方法,将会把线程池的状态标记为STOP,此时线程池也不会接收任何新的任务,并且立即停止正在执行任务的线程,将队列里的任务返回给调用者。
4.合理的配置线程池
要想合理的配置线程池,就要知道任务的特性
任务类型分为CPU密集型和IO密集型
性质不同的任务采用不同规模的线程池进行处理,对于CPU密集型的任务可以配置尽可能小的线程,对于IO密集型的任务可以配置尽可能多的线程。
参考资料:《Java并发编程的艺术》
网友评论