为什么使用线程池
- 降低资源消耗,提高线程利用率,降低创建和销毁线程的消耗。
- 提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,在执行。
- 提高线程的可管理性,线程是稀缺资源,使用线程可统一分配调优监控
线程池参数
- corePoolSize代表核心线程数,也就是正常情况创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
- maxinumPoolSize代表最大线程数,它于核心线程数相对应,代表最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线总数不会超过最大线程数
- keepAliveTime、unit表示超过核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超过核心线程的部分线程如果空闲一定的时间则会被消除,我们可以通过setKeepAliveTime来设置空闲时间
- workQueue用来存放执行的任务,假设我们现在核心线程已经被使用,还有任务进来则全部放入队列,直到整个队列放满担任务还在持续进入则会开始创建的新的线程
- ThreadFactory实际上是一个线程工厂,用来生产线程执行任务。我们可以选择使用默认的创建工厂,产生的线程都在同一组内,拥有相同的优先级,且都不是守护线程。当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
- Handle任务拒绝策略,有两种情况,第一种是当我们调用shutdown等方法关闭线程池后,这时候即使线程池内部还有没有执行玩的任务正在执行,但是由于线程池已经关闭,我们在继续向线程池提交任务就会再到拒绝。另一种情况就是当达到最大线程数,线程池已经没有能力继续提交新的任务时,这时也就拒绝
AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中最老的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
线程池处理流程
线程池中阻塞队列的作用?
一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留当前想要继续入队的任务。阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。阻塞队列自带阻塞和唤醒的功能,不需要而外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用cpu资源
为什么是先添加队列而不是先创建最大线程?
在创建新线程的时候,是需要获取全局锁的,这时候其它的就得阻塞,影响整体效率。
线程池复用的原理
线程池将线程和任务进行解耦,线程是线程,任务时任务,摆脱了之前通过Thread创建线程时的一个必须对应有一个任务的限制。在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用Thread.start()来创建新线程,而是让每个线程去执行一个循环任务,在这个循环任务中不停检查是否有任务需要被执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程将所有的run方法串联起来。
线程池创建大小
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式 (1.5/0.5+1)×8=32
一般我们会设置 CPU核数+1 防止由于其他因素导致线程阻塞等。
那么换算过来的话就是(1.5/0.5+1)×(8+1)=36
但是现在计算机cpu都是超线程技术如果是8和的话那么使用超线程技术的话那么就是16核
所以(1.5/0.5+1)×(8×2+1)=68