在Web开发中,服务器需要接受并且处理请求,所以会为一个请求分配一个线程去处理,如果并发的请求数量很大,但是请求的时间很短,那么就会频繁的创建和销毁线程,造成额外的CPU隐患,如此一来,会大大降低系统的效率。
为了解决上述的问题,于是我们可以利用Executors创建不同的线程池满足不同场景的需求,目前这个类提供了五种创建线程池的方法:
newFixedThreadPool(int nThreads)
:指定工作线程数量的线程池newCachedThreadPool()
:处理大量短时间工作任务的线程池,(1)试图缓存线程并重用,当无缓存线程可用的时候,就会创建出新的工作线程
(2)如果线程限制的时间超过阈值,则会被终止并移除缓存
(3)系统长时间闲置的时候,不会消耗什么资源
3. newSingleThreadExecutor()
:创建唯一的工作者线程来执行任务,如果线程异常结束,会有课另一个线程取代他
4. newSingleThreadScheduledExecutor()
与newScheduledThreadPool(int corePoolSize)
:定时或者周期性的工作调度,两者的区别在于单一线程还是多个线程
5. newWorkStealingPool
:内部创建ForkJoinPool,利用working-stealing算法,并行地处理任务,不保证处理顺序。
为了了解第五条所说的ForkJoinPool,这里就来介绍一下Fork/Join框架:
这是Java7提供的一个并行处理任务的框架,它能把大人物分割成若干个小人物,最终汇总每一个任务结果后得到大人物结果的框架。
可以将递归的方法,拆成一个一个小的任务,分治解决。
从原理上来说,和MapReduce的原理是一样的。
它会将任务分发给任务池中的工作线程,使用工作窃取即WorkStealing算法(某个线程从其他队列里窃取任务来执行)。
上面我们提到,Fork/Join就是将大任务切割成小任务去执行,最后将小任务的结果整理起来得到大人物的结果,它会对这些任务分配单独的队列,单独的线程去执行,那么,这里就会出现一种情况,有些线程的队列任务已经完成,但是有些队列的任务还没有完成,所以,WorkStealing算法,就体现了作用,它能在线程处理任务之后,窃取busy线程的任务去执行,但是为了防止出现竞争任务的情况,一般我们都采用双端队列。
被窃取任务的队列永远都从头部获取执行,窃取任务的队列永远从尾部获取执行。
接下来我们就来介绍一下Executor的框架:
Executor框架是一组执行策略调用,资源调度和控制的异步任务框架,目的是将任务提交和任务如何运行分离开来的机制。
而JUC下一共有三个Executor接口
他的作用可能是创建一个新线程并且立刻使用,也可能是使用已有的运行线程运行当前的任务,也有可能是视设置线程池的容量或者阻塞队列的容量来决定是否将传入的线程放入阻塞队列中,或者拒绝接受传入的线程
我们之前创建单线程去跑的时候是这样的:
Thread t = new Thread();
t.start();
但是使用了Executor之后,就会变成这样
Thread t = new Thread();
executor.execute(t);
ExecutorService扩展了Executor的接口,额外具备了管理执行器和任务生命周期的方法,提交任务机制更加完善。
其中的亮点在于submit方法,他其中的一个方法是接收Callable参数的,我们已经知道Callable接口解决了Runnable无法进行return的缺点。
扩展了ExecutorService,支持Future和定期执行任务。
我们使用了不同的Executor的实现来解决不同场景的需求。
一般情况下,我们是用Executor提供了五种创建线程池的方法就足够了,但是还是有一些特殊的场景需要我们使用ThreadPoolExecutor等构造函数去创建。
下面我们来分析一下线程池的设计与实现:
下图演示了任务从提交到线程池内如何处理任务再到处理完成的引用逻辑:
那么,我们也来看看ThreadPoolExecutor最有价值的的构造器的源码:
下面来一一分析:
此外,线程池还提供了四种策略来解决线程池的饱和问题:
且在新任务提交execute之后,会执行以下的判断:
线程池一共有以下几种状态: