文章目录
自定义线程池七个参数
线程池的构造函数有7个参数,分别是 corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。
1.corePoolSize
线程池核心线程大小
线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。
2.maximumPoolSize
线程池最大线程数量
一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
3.keepAliveTime
空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
4.unit超时时间
keepAliveTime的计量单位
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue
5.工作队列workQueue
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
ArrayBlockingQueue 有界队列
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
LinkedBlockingQuene 无界队列
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
SynchronousQuene 直接提交
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
PriorityBlockingQueue 优先级队列
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和SynchronousQueue。
线程池的排队策略与BlockingQueue有关 (可查看线程池 newScheduledThreadPool 的创建方式)。
6.threadFactory
用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如 设定线程名、设置daemon和优先级等等
7.handler拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
CallerRunsPolicy 只用调用者所在线程来运行任务
该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。(哪个线程提交的就哪个线程自己执行任务)
AbortPolicy 直接抛出异常(默认)
该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
DiscardPolicy 不处理,丢弃掉
该策略下,直接丢弃任务,什么都不做。
DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务
该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
四种拒绝策略
* 1. new ThreadPoolExecutor.AbortPolicy() 超出队列,抛出异常,会丢弃
* 2. new ThreadPoolExecutor.DiscardPolicy() 超出队列,不会抛出异常,会丢弃
* 3. new ThreadPoolExecutor.DiscardOldestPolicy() 尝试和第一个竞争,不会抛出异常,会丢弃
* 4. new ThreadPoolExecutor.CallerRunsPolicy() 那里来的去哪里 让main线程执行
*
*
图示
小妙招
通过6ThreadFactory线程池工厂创建线程池,线程池进入线程需要先在5workQueue工作队列中进行排队,能否进入线程之中是由两个参数决定的,1corePoolSize核心线程数和2maxPoolSize最大线程数,2-1就是临时线程,当核心线程和工作队列满时会创建临时线程,在一定的时间之后就会销毁,设置3keepAliveTime临时线程存活时间+4.unit超时时间(临时线程存活时间单位),临时线程也满了之后线程池就会采取7handler拒绝策略
常用线程池
1.FixedThreadPool
corePoolSize=maxPoolSize,LinkedBlockingQuene基于链表的无界阻塞队列(其实最大容量为Interger.MAX 2的31次方减1)FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
2.CatchedThreadPool缓存线程池
只有最大线程没有核心线程,所有的线程都是临时的,因此没有线程的上限SynchronousQuene 0 (其实最大容量为Interger.MAX 2的31次方减1)
3.ScheduledThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行
4.SingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
线程池本质
对比三种线程池的执行速度
CatchedThreadPool
会随着我们任务的多少创建多少个worker来执行任务,有多少个任务就会创建多少个线程
FixedThreadPool
worker(线程)数量决定了执行速度快慢
SingleThreadExecutor
一个公司只有一个员工,所有的任务都由一个人完成
为什么大厂不推荐自带的线程池?
各个线程池的缺陷
CatchedThreadPool
像保险公司,零成本,没有员工,有任务之后随便拉一个人就可以干这个活,这种公司最赚钱
有多少个任务就会创建多少个线程,创建线程占用CPU,所以容易造成CPU占用100%
FixedThreadPool
像国企,效率低,如果任务很多,都会存放到任务队列里,任务队列没有限制 ,会造成oom
SingleThreadExecutor
和FixedThreadPool一样,采用LinkedBlockingQueue,容易造成oom
推荐使用什么线程池?
刚刚上面的三个底层采用的都是ThreadPoolExecutor(自带的线程池),我们可以根据自己的业务自动调配(主要就是线程池的7个参数灵活使用)
总结
线程池其实还有很多知识要学,小编只学到了冰山一角,希望大家多提意见哦!