为什么使用线程池?线程池的作用
- 降低资源消耗:提高线程利用率,降低创建和销毁线程的消耗。
- 提高响应速度:任务来了,直接拿线程池的的线程可用可执行,而不是先创建线程,再执行。
- 提高线程的可管理性:线程是稀缺资源,使用线程池可以统一分配销毁。
PS:可以类比我们打网球的场景,线程池是装网球的框子,线程是一个个网球,每次训练打网球时会在筐子里准备一筐子网球,每次用一个拿一个,即用即拿,用完之后统一回收
线程池有哪些状态?
- Running:这是最正常的状态,可接收新任务,也可处理等待队列中的任务
- ShutDown: 不再接收新任务的提交,但会继续处理等待队列中的任务。
- Stop: 不接收新任务的提交,也不处理等待队列中的任务,中断正在执行任务的线程。
- Tidying: 所有任务都销毁了,workcount为0,线程池状态在转换为tidying状态时,会执行钩子方法terminated()
- Terminated: terminated()方法执行结束后,线程池的状态就会变成这个。
线程池有哪些参数?
- corePoolSize: 核心线程数,也就是正常情况下能创建的线程数,这些线程创建后并不会消除,而是一种常驻线程。
- workQueue: 阻塞队列,用来放等待执行的任务。假设核心线程数被使用完,还有任务进来放队列里,若队列放满则开始创建救急线程。
- maximumPoolSize: 最大线程数目。(注:最大线程数=核心线程数+救急线程数)
- keepALiveTime:生存时间(针对救急线程),超出生存时间的非核心线程数会被消除。
- unit:生存时间的时间单位,针对救急线程
- ThreadFactory: 线程工厂,用来生产线程执行任务。我们可以使用默认的线程工厂:产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程。我们也可以自定义线程工厂,可以根据业务制定不同的线程工厂。
- Handler: 任务拒绝策略
任务拒绝策略有哪些?
- AbortPolicy(默认策略): 让调用者抛出rejectExecutionException异常
- CallerRunsPolicy: 让调用者运行任务
- DiscardPolicy: 放弃本次任务
- DiscardOldestPolicy: 放弃队列中最早的任务取而代之
线程池的处理流程具体是怎样的?
- 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。
- 当线程数达到 corePoolSize 并没有线程空闲时,这时再加入任务,新加的任务会被加入 workqueen队列排队,直到有空闲的线程。
- 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。
- 如果救急线程数也达到上限,这时再来任务会执行 拒绝策略。
阻塞队列的作用是什么?
一般队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。 阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用cpu资源。
为什么新任务来了是先添加队列而不是先创建最大线程?
在创建线程的时候,是要获取全局锁的,这个时候其他的就得阻塞,影响了整体效率。 就好比工厂企业里有10个(core)正式工的名额,最多招10个正式工,要是人数超过正式工人数(take>core)的情况下,工厂领导(线程池)不是先扩招工人,还是这10个人,但是任务可以稍微积压一下,即先放到队列去(代价低)。10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工加班忍耐极限了,就找外包(救急线程)帮忙了。要是正式工加外包还是不能完成任务,那新来的任务也会被领导拒绝。
创建线程池有哪几种方式?
- newFiexedThreadPool
核心线程数=最大线程数,没有救急线程被创建,因此也无需超时时间;阻塞队列是无界的,可以放任意数量的任务。
总结:适用于任务量已知,相对耗时的任务
@Slf4j
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService poolabc = Executors.newFixedThreadPool(3);
poolabc.execute(()->log.info("1"));
poolabc.execute(()->log.info("2"));
poolabc.execute(()->log.info("3"));
}
}
- newCachedThreadPool
核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间是60s,意味着全部都是救急线程(60s后可以回收),救急线程可以无限创建 队列采用了SynchronousQUeue,实现特点是它没有容量,没有线程来取是放不进去的
总结:整个线程池表现为线程数根据任务量不断增长,没有上限,当任务执行完毕,空闲一分钟后释放线程。 适用于任务数比较密集,但每个任务执行时间比较短的情况。
- newSingleThreadPool 只有一个核心线程。任务多于1时,会放入无界队列排队,任务执行完毕,唯一的线程也不会释放。
若自己创建一个单线程执行任务和newSingleThreadPool线程池执行任务的区别?
如果自己创建的任务执行失败而终止,那么没有任何补救措施,而线程池还会创建一个线程,保证池的正常工作。
newScheduledThreadPool schedule可以延迟执行任务
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.info("start..");
pool.schedule(()->{
try{
TimeUnit.SECONDS.sleep(2);
log.info("1");
}catch (InterruptedException e){
e.printStackTrace();
}
}, 1, TimeUnit.SECONDS);
pool.schedule(()->{
log.info("2");
}, 1, TimeUnit.SECONDS);
运行结果截图
误用线程池带来的问题
可能导致内存溢出