-
1.为什么需要线程池呢,它有哪些好处?
答:线程在创建并执行时会从用户态进入到内核态,这个过程会非常影响效率,有时这个过程可能甚至比执行线程内的代码还要费时费力,所以,为了解决这种问题,我们就需要引入线程池了。
线程池可以使我们创建好的线程被多次使用,使单个线程不再只能执行一个任务,进而大大提高我们的效率。不仅如此,通过对线程池的理解和方法的运用,我们也可以对我们的多线程程序有更好的掌控。
-
2.尝试使用自己的理解来说明一下手动创建线程池需要的7个参数(英文不说也行,主要是参数的作用)
答:首先是核心线程数(int corePoolSize),也就是线程的下限
接下来是最大线程数(int maximumPoolSize ),即线程的上限
然后是空闲时的等待时间(long keepAliveTime ),线程如果没有活干了,就会加入等待队列,超过传入等待时间后,就会自动销毁了。
再往下是等待时间的单位(TimeUnit unit),可以是秒、分、小时等等
剩下的两个参数分别是进程工厂(ThreadFactory threadFactory)和拒绝策略(RejectedExecutionHandler handler),这两个都不是必须传入的,我们可以根据需求自定义进程工厂或是拒绝策略,也可以不传入参数,线程池会使用默认的进程工厂和拒绝策略来进行处理。
进程工厂用来创建新的进程
当任务队列已满并且进程池内的进程数量达到上限还都在忙碌时,程序则会采取拒绝策略,拒绝新任务的加入
juc提供了多种拒绝策略:
AbortPolicy 拒绝任务,并抛出异常
DiscardPolicy 拒绝任务,没有任何提示
DiscardOldestPolicy 拒绝最总加入等待瑞列的任务,新任务加入队列
CallerRunsPolicy 拒绝任务,将任务交还给发起任务的线程
我们也可以通过重写方法来自定义自己的拒绝策略
-
3.试着解释一下线程池的内部机制
答:如图所示,每当新任务准备执行时,我们先看此时的线程数量是否达到下限,如果没达到,就新建一个线程,用来执行任务。如果已经达到了下限,代表此时线程足够,不需要创建新线程了,我们就看任务队列是否还有空闲,如果有空闲就把它加入任务队列,等待执行,如果任务队列也满了,代表线程忙不过来了,我们就看看线程数量是否达到上限,如果未达上限,我们就新建一个线程用来处理这个任务,如果列表也满了,线程也达到上限了,我们就无法完成这个任务了,此时我们只能采用拒绝策略,来拒绝这个任务了。
现在我们回到之前,假设在进入进程池后,任务已经被成功完成了,此时线程就会前往任务队列查看是否还有待完成的任务了,如果有,我们就继续取出一个任务执行,循环往复这个过程,直到任务列表被执行完毕,如果队列里已经没有任务了,代表工作告一段落了,线程会进入等待队列等待新任务的出现,如果在传入的等待时间内,始终没有新的任务出现,通常来讲,超过下限的线程将会被销毁。
-
4.使用线程池的submit方法时,传入的参数根据实现Runnable和Callable接口的不同,所产生的效果有哪些差异呢?
不管我们的对象实现的是Runnable还是Callable接口,如果我们执行后都不需要获得任何返回值,那么在使用submit方法后基本相同,我们都会获得一个Future对象,都可以使用Future的isDone方法来判断线程是否执行完毕,但如果想获得执行后的结果,就只有实现Callable接口的对象可以使用Future的get方法获得返回值了。