一.什么是线程池?
首先说一下它的定义,线程池是一种多线程处理形式,当有任务提交到线程池时,可以直接使用线程池内的线程来执行任务。而如果不使用线程池的话,每次任务的执行都需要进行一次线程的创建于下销毁,这个过程的开销是比较大的,甚至超过了业务本身的执行开销。使用线程池就可以对这个问题进行优化。
JUC下的Executors类提供了Executor接口的实现方式用来创建线程池。
而线程池对象实际上是ThreadPoolExecutor对象;java本身也给我们封装好了几种常用的线程池。
二.说说几种常见的线程池及其使用场景
1.newFixedThreadPool:这是一个固定长度大小的线程池;当有新的任务提交且线程池中线程数还没达到最大值时,会不断创建新的线程到线程池中,直到达到最大值;超出的线程就会到任务阻塞队列中进行等待。用的是LinkedBlockingQueue这个任务阻塞队列。
适用于并发数量比较稳定的场景。
2 newCachedThreadPool:这是一个可缓存的线程池。容量有上限,可以在调用静态工厂方法时显式传入,而如果不传入参数的话,默认最大容量为Integer.MAX_VALUE。当任务所需线程数小于corePoolSize(核心线程数)时,直接在线程池创建新的线程;当所需线程大于corePoolSize时,当前任务就会进入任务拥塞队列之中;而所需线程数大于线程池最大容量且队列已满的时候,就会执行拒绝策略来处理。该线程池所使用的队列是SynchronizedQueue。并且启用TimeOut机制:当线程池中的非核心线程闲置超过60s时,该线程就会被销毁。
适用于线程数量多且执行时间短的场景。
3 newSingleThreadExecutor:单线程化的线程池,只有唯一的一个工作线程来执行任务,保证所有任务都能按照指定顺序(先来先服务,最高优先级等)来运行
4 newScheduledThreadPool:相比之下功能更强大,支持定时以及周期性任务的执行。
适用于需要进行定时或周期性任务的场景。
三.线程池的几个重要参数?
线程池的参数其实就是ThreadPoolExecutor类的参数。
1.corePoolSize就是线程池中的核心线程数量,这几个核心线程在闲置了特定的时间之后也不会被回收(当然也可以设定为会被回收)
2.maximumPoolSize:线程池中可以容纳的最大线程的数量。
3.keepAliveTime:非核心线程的最大空闲时间,空闲超过了这个时间的非核心线程会被自动回收
4.util:计算keepAliveTime的时间单位,如秒,毫秒。
5.workQueue:任务拥塞队列。当任务所需线程超过了线程池核心线程数时,任务就要进入队列中进行等待,一般是按照先来先服务的调度策略。
6.threadFactory:就是创建线程的线程工厂。
7.handler:拒绝策略。当线程池达到最大容量且队列满了之后,对于超额的任务的处理方法。
四.说说线程池的拒绝策略吧?
当任务提交到线程池,而线程池已经达到最大容量,且队列已满,此时就会对提交的任务拒绝服务,就是拒绝策略。RejectedExecutionHandler接口提供途径去自定义方法去实现拒绝策略。而在ThreadPoolExecutor中已经有四种拒绝策略了。
AbortPolicy:拒绝服务时直接抛出异常,阻止线程池的正常工作。
CallerRunPolicy:直接在调用者线程中运行当前的任务。
DiscardPolicy:直接将该方法丢弃,不进行处理。
DiscardOrdestPolicy:丢弃队列中最老的一个任务,并重新提交当前任务。
除此之外还可以根据实际需要自定义拒绝策略,只要实现RejectedExecutionHandler接口就可以了。
五.execute和submit的区别。
execute方法是线程池中最原生的方法,也是Executor接口中唯一一个方法,它可以接受Callable对象或者Runnable对象。而submit方法其实就是调用了execute方法,只能接受Callable对象。
最主要的区别在于execute方法执行线程没有返回值,而submit方法执行会返回一个Future类对象,通过该对象的get方法可以获取线程执行的返回值。另外Future类的get方法是阻塞操作,当submit多个Callable时,只有所有任务都执行完成了,才能使用get按照任务提交顺序依此获得返回值。
六.线程池有哪几种工作队列?
1.ArrayBlockingQueue:基于数组实现的有界阻塞队列,采取先进先出原则对元素进行排序。
2.LinkedBlockingQueue:基于链表实现的阻塞队列,也采用先进先出来排序元素,静态工厂方法创建的newFixedThreadPool()线程池就使用了这种队列。
3.SynchronousQueue:一个不储存元素的阻塞队列。特点为在当前任务进行插入同时,上一个进入队列的任务必须被移除,反之亦然;如果不满足这个条件,操作就会一直处于阻塞状态。静态工厂方法创建的newCachedThreadPool就用这种队列。
4.DelayedWorkQueue:类似于LinkedBlockingQueue,也是基于链表实现。但是元素排序方式是按照最高优先级来进行。
七.说一下Future类的get()方法?
当Callble对象作为参数被传入线程池的submit()方法进行执行时,方法会返回一个Future类对象,而Future对象的get方法可以获取到对应线程执行的返回值(也就是Callable对象call方法的返回值)。
需要提到的是,Future类的get方法是一个阻塞操作,也就是说只有当线程执行完毕并返回值之后,get方法才能获取到返回值,否则将一直阻塞直至线程执行完毕返回值。这样的话就会有资源浪费的情况发生,比如submit提交了一系列任务之后返回一系列Future对象,对Future对象逐个调用get方法可能会发生阻塞,所以可能造成一种情况比如后面的线程早已经执行完毕了,但此时还需要等待前面的线程执行完,才能获得到返回值。
解决方法主要有两个:
Future类中还有一个get的重载方法,该方法传入一个时间大小参数和一个时间单位参数,规定get方法最多只能阻塞有限的时间,超过有限时间将放弃接受返回值,退出阻塞。但这个方法有点治标不治本,因为本质上它还是要阻塞一段时间。
可以使用CompletionService类,该类的构造需要传入一个线程池对象作为参数。使用方法和线程池相同,也拥有submit()方法。和Future类不一样的是,CompletionService对象获取线程执行返回值的操作是不阻塞操作,也就是说它不关心get方法调用顺序,而是只要有线程执行完毕并且返回值,就可以直接获取到。